From acfe81dd7baf26b767bbd41f6a44765a3bab8117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 26 Feb 2025 13:45:20 -0300 Subject: [PATCH 01/17] chore: upgrade wallet-lib to v1.14.1 --- package.json | 2 +- packages/common/package.json | 2 +- packages/daemon/package.json | 2 +- packages/wallet-service/package.json | 2 +- yarn.lock | 235 +++++++++++++++++++-------- 5 files changed, 171 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index 911a7b95..c2345eae 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@aws-sdk/client-apigatewaymanagementapi": "3.540.0", "@aws-sdk/client-lambda": "3.540.0", "@aws-sdk/client-sqs": "3.540.0", - "@hathor/wallet-lib": "0.39.0", + "@hathor/wallet-lib": "1.14.1", "@wallet-service/common": "1.5.0", "bip32": "^4.0.0", "bitcoinjs-lib": "^6.1.5", diff --git a/packages/common/package.json b/packages/common/package.json index 1edea33c..10f44304 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -8,7 +8,7 @@ "test": "jest --runInBand --collectCoverage --detectOpenHandles --forceExit" }, "peerDependencies": { - "@hathor/wallet-lib": "0.39.0" + "@hathor/wallet-lib": "1.14.1" }, "dependencies": { "@aws-sdk/client-lambda": "3.540.0", diff --git a/packages/daemon/package.json b/packages/daemon/package.json index d772c757..fcbaca36 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -46,7 +46,7 @@ "typescript": "4.9.5" }, "peerDependencies": { - "@hathor/wallet-lib": "0.39.0", + "@hathor/wallet-lib": "1.14.1", "@wallet-service/common": "1.5.0" }, "dependencies": { diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index 87e75d00..264412c2 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -40,7 +40,7 @@ "winston": "3.13.0" }, "peerDependencies": { - "@hathor/wallet-lib": "0.39.0", + "@hathor/wallet-lib": "1.14.1", "@wallet-service/common": "1.5.0" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 5dd283ab..f923db7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2160,19 +2160,23 @@ __metadata: languageName: node linkType: hard -"@hathor/wallet-lib@npm:0.39.0": - version: 0.39.0 - resolution: "@hathor/wallet-lib@npm:0.39.0" +"@hathor/wallet-lib@npm:1.14.1": + version: 1.14.1 + resolution: "@hathor/wallet-lib@npm:1.14.1" dependencies: - axios: "npm:^0.18.0" - bitcore-lib: "npm:^8.25.10" - bitcore-mnemonic: "npm:^8.25.10" - crypto-js: "npm:^3.1.9-1" - isomorphic-ws: "npm:^4.0.1" - lodash: "npm:^4.17.11" - long: "npm:^4.0.0" - ws: "npm:^7.2.1" - checksum: 10/1a49bb3f335b4d9f2005df4459f11687a2ccf4595afa45282b43f9127e2b4360ee37d2df6fe551007d83dc7bcbc518f0228f36247a1b147d702f9bd1cae66705 + abstract-level: "npm:1.0.4" + axios: "npm:1.7.2" + bitcore-lib: "npm:8.25.10" + bitcore-mnemonic: "npm:8.25.10" + buffer: "npm:6.0.3" + crypto-js: "npm:4.2.0" + isomorphic-ws: "npm:5.0.0" + level: "npm:8.0.1" + lodash: "npm:4.17.21" + long: "npm:5.2.3" + queue-microtask: "npm:1.2.3" + ws: "npm:8.17.1" + checksum: 10/e95fafa78a0a4a24cb4b6ee49c8abfb4754d347c159078e78486f57720499a58619339652c041313025d10b440b6069aee8676857890de7c0ef69c9c3870296a languageName: node linkType: hard @@ -5000,7 +5004,7 @@ __metadata: typescript: "npm:5.4.3" winston: "npm:3.13.0" peerDependencies: - "@hathor/wallet-lib": 0.39.0 + "@hathor/wallet-lib": 1.14.1 languageName: unknown linkType: soft @@ -5185,6 +5189,21 @@ __metadata: languageName: node linkType: hard +"abstract-level@npm:1.0.4, abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.4": + version: 1.0.4 + resolution: "abstract-level@npm:1.0.4" + dependencies: + buffer: "npm:^6.0.3" + catering: "npm:^2.1.0" + is-buffer: "npm:^2.0.5" + level-supports: "npm:^4.0.0" + level-transcoder: "npm:^1.0.1" + module-error: "npm:^1.0.1" + queue-microtask: "npm:^1.2.3" + checksum: 10/8edf4cf55b7b66b653296f53a643bcf1501074be099d8c44351595cd33f769b7b2aed216d5fffe1c99ebea4acf14f5ae093e98baa60ea1d236ea8a3387350ebb + languageName: node + linkType: hard + "acorn-import-assertions@npm:^1.9.0": version: 1.9.0 resolution: "acorn-import-assertions@npm:1.9.0" @@ -5753,13 +5772,14 @@ __metadata: languageName: node linkType: hard -"axios@npm:^0.18.0": - version: 0.18.1 - resolution: "axios@npm:0.18.1" +"axios@npm:1.7.2": + version: 1.7.2 + resolution: "axios@npm:1.7.2" dependencies: - follow-redirects: "npm:1.5.10" - is-buffer: "npm:^2.0.2" - checksum: 10/e89e662c4998a2617bd7c34b9444a5d0b2029e3df952e5aa8756c5882e3f1d5a7143d10b5d6435d9276a71a199a3494672d7f1cc72cc8e598224feaf99793ef7 + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.0" + proxy-from-env: "npm:^1.1.0" + checksum: 10/6ae80dda9736bb4762ce717f1a26ff997d94672d3a5799ad9941c24d4fb019c1dff45be8272f08d1975d7950bac281f3ba24aff5ecd49ef5a04d872ec428782f languageName: node linkType: hard @@ -6041,7 +6061,7 @@ __metadata: languageName: node linkType: hard -"bitcore-lib@npm:^8.25.10, bitcore-lib@npm:^8.25.47": +"bitcore-lib@npm:^8.25.10": version: 8.25.47 resolution: "bitcore-lib@npm:8.25.47" dependencies: @@ -6069,18 +6089,6 @@ __metadata: languageName: node linkType: hard -"bitcore-mnemonic@npm:^8.25.10": - version: 8.25.47 - resolution: "bitcore-mnemonic@npm:8.25.47" - dependencies: - bitcore-lib: "npm:^8.25.47" - unorm: "npm:^1.4.1" - peerDependencies: - bitcore-lib: ^8.20.1 - checksum: 10/fca2d1471b5b4ff504111331d2a350aa2c2439a06ae56242deb9c6fdf4dcab7f1b6017b855c1c48bd889d5e48aae8fabb0dea1c6401880abbaa3d23bb0ced2d7 - languageName: node - linkType: hard - "bl@npm:^1.0.0": version: 1.2.3 resolution: "bl@npm:1.2.3" @@ -6181,6 +6189,18 @@ __metadata: languageName: node linkType: hard +"browser-level@npm:^1.0.1": + version: 1.0.1 + resolution: "browser-level@npm:1.0.1" + dependencies: + abstract-level: "npm:^1.0.2" + catering: "npm:^2.1.1" + module-error: "npm:^1.0.2" + run-parallel-limit: "npm:^1.1.0" + checksum: 10/e712569111782da76853fecf648b43ff878ff2301c2830a9e7399685b646824a85f304dea5f023e02ee41a63a972f9aad734bd411069095adc9c79784fc649a5 + languageName: node + linkType: hard + "browserify-aes@npm:^1.0.6": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" @@ -6343,6 +6363,16 @@ __metadata: languageName: node linkType: hard +"buffer@npm:6.0.3, buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 + languageName: node + linkType: hard + "buffer@npm:^5.2.1, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" @@ -6510,6 +6540,13 @@ __metadata: languageName: node linkType: hard +"catering@npm:^2.1.0, catering@npm:^2.1.1": + version: 2.1.1 + resolution: "catering@npm:2.1.1" + checksum: 10/4669c9fa5f3a73273535fb458a964d8aba12dc5102d8487049cf03623bef3cdff4b5d9f92ff04c00f1001057a7cc7df6e700752ac622c2a7baf7bcff34166683 + languageName: node + linkType: hard + "chalk@npm:^2.4.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -6642,6 +6679,20 @@ __metadata: languageName: node linkType: hard +"classic-level@npm:^1.2.0": + version: 1.4.1 + resolution: "classic-level@npm:1.4.1" + dependencies: + abstract-level: "npm:^1.0.2" + catering: "npm:^2.1.0" + module-error: "npm:^1.0.1" + napi-macros: "npm:^2.2.2" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.3.0" + checksum: 10/11f9362301477cb5cf3b147e5846754e0e4296231e265145101403f4a5cb797a685b6a9b6b4c880a42b05772f846a222a5a7a563262ca15b5ca03e25e9a805db + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -7075,10 +7126,10 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^3.1.9-1": - version: 3.3.0 - resolution: "crypto-js@npm:3.3.0" - checksum: 10/d7e11f3a387fb143be834e1a25ecf57ead6f5765e90fbf3aed9cead680cc38b1d241718768b7bfec448a843f569374ea5b5870ac7a8165e4bfa1915f0b00c89c +"crypto-js@npm:4.2.0": + version: 4.2.0 + resolution: "crypto-js@npm:4.2.0" + checksum: 10/c7bcc56a6e01c3c397e95aa4a74e4241321f04677f9a618a8f48a63b5781617248afb9adb0629824792e7ec20ca0d4241a49b6b2938ae6f973ec4efc5c53c924 languageName: node linkType: hard @@ -7144,15 +7195,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:=3.1.0": - version: 3.1.0 - resolution: "debug@npm:3.1.0" - dependencies: - ms: "npm:2.0.0" - checksum: 10/f5fd4b1390dd3b03a78aa30133a4b4db62acc3e6cd86af49f114bf7f7bd57c41a5c5c2eced2ad2c8190d70c60309f2dd5782feeaa0704dbaa5697890e3c5ad07 - languageName: node - linkType: hard - "debug@npm:^2.2.0, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -8932,16 +8974,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:1.5.10": - version: 1.5.10 - resolution: "follow-redirects@npm:1.5.10" - dependencies: - debug: "npm:=3.1.0" - checksum: 10/7cf3bb10dbffabce317d1de3e3f85ea8c47fea388c4bc3da1052fd6bbb7b036772f48eed2f154d63ac39c49ab5fe4df0dda94b8e0c54eda8ca2edb001b2550b7 - languageName: node - linkType: hard - -"follow-redirects@npm:^1.10.0": +"follow-redirects@npm:^1.10.0, follow-redirects@npm:^1.15.6": version: 1.15.9 resolution: "follow-redirects@npm:1.15.9" peerDependenciesMeta: @@ -9684,7 +9717,7 @@ __metadata: "@aws-sdk/client-apigatewaymanagementapi": "npm:3.540.0" "@aws-sdk/client-lambda": "npm:3.540.0" "@aws-sdk/client-sqs": "npm:3.540.0" - "@hathor/wallet-lib": "npm:0.39.0" + "@hathor/wallet-lib": "npm:1.14.1" "@types/jest": "npm:29.5.13" "@typescript-eslint/eslint-plugin": "npm:^7.4.0" "@typescript-eslint/parser": "npm:^7.4.0" @@ -10086,7 +10119,7 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.2": +"is-buffer@npm:^2.0.5": version: 2.0.5 resolution: "is-buffer@npm:2.0.5" checksum: 10/3261a8b858edcc6c9566ba1694bf829e126faa88911d1c0a747ea658c5d81b14b6955e3a702d59dabadd58fdd440c01f321aa71d6547105fd21d03f94d0597e7 @@ -10465,6 +10498,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-ws@npm:5.0.0": + version: 5.0.0 + resolution: "isomorphic-ws@npm:5.0.0" + peerDependencies: + ws: "*" + checksum: 10/e20eb2aee09ba96247465fda40c6d22c1153394c0144fa34fe6609f341af4c8c564f60ea3ba762335a7a9c306809349f9b863c8beedf2beea09b299834ad5398 + languageName: node + linkType: hard + "isomorphic-ws@npm:^4.0.1": version: 4.0.1 resolution: "isomorphic-ws@npm:4.0.1" @@ -11427,6 +11469,34 @@ __metadata: languageName: node linkType: hard +"level-supports@npm:^4.0.0": + version: 4.0.1 + resolution: "level-supports@npm:4.0.1" + checksum: 10/e2f177af813a25af29d15406a14240e2e10e5efb1c35b03643c885ac5931af760b9337826506b6395f98cf6b1e68ba294bfc345a248a1ae3f9c69e08e81824b2 + languageName: node + linkType: hard + +"level-transcoder@npm:^1.0.1": + version: 1.0.1 + resolution: "level-transcoder@npm:1.0.1" + dependencies: + buffer: "npm:^6.0.3" + module-error: "npm:^1.0.1" + checksum: 10/2fb41a1d8037fc279f851ead8cdc3852b738f1f935ac2895183cd606aae3e57008e085c7c2bd2b2d43cfd057333108cfaed604092e173ac2abdf5ab1b8333f9e + languageName: node + linkType: hard + +"level@npm:8.0.1": + version: 8.0.1 + resolution: "level@npm:8.0.1" + dependencies: + abstract-level: "npm:^1.0.4" + browser-level: "npm:^1.0.1" + classic-level: "npm:^1.2.0" + checksum: 10/a9c6d1fc50e30b2cc80b3c975b34de0eb12daab7fb4f8a546a28303705a45685340a904544fcd32e9a380fae7c62474ebd9cdb0108021ddbc7b88dd9c913f126 + languageName: node + linkType: hard + "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -11718,6 +11788,13 @@ __metadata: languageName: node linkType: hard +"long@npm:5.2.3, long@npm:^5.0.0, long@npm:^5.2.1": + version: 5.2.3 + resolution: "long@npm:5.2.3" + checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 + languageName: node + linkType: hard + "long@npm:^4.0.0": version: 4.0.0 resolution: "long@npm:4.0.0" @@ -11725,13 +11802,6 @@ __metadata: languageName: node linkType: hard -"long@npm:^5.0.0, long@npm:^5.2.1": - version: 5.2.3 - resolution: "long@npm:5.2.3" - checksum: 10/9167ec6947a825b827c30da169a7384eec6c0c9ec2f0b9c74da2e93d81159bbe39fb09c3f13dae9721d4b807ccfa09797a7dd1012f5d478e3e33ca3c78b608e6 - languageName: node - linkType: hard - "lowercase-keys@npm:^2.0.0": version: 2.0.0 resolution: "lowercase-keys@npm:2.0.0" @@ -12193,6 +12263,13 @@ __metadata: languageName: node linkType: hard +"module-error@npm:^1.0.1, module-error@npm:^1.0.2": + version: 1.0.2 + resolution: "module-error@npm:1.0.2" + checksum: 10/5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 + languageName: node + linkType: hard + "moment-timezone@npm:^0.5.43": version: 0.5.43 resolution: "moment-timezone@npm:0.5.43" @@ -12322,6 +12399,13 @@ __metadata: languageName: node linkType: hard +"napi-macros@npm:^2.2.2": + version: 2.2.2 + resolution: "napi-macros@npm:2.2.2" + checksum: 10/2cdb9c40ad4b424b14fbe5e13c5329559e2b511665acf41cdcda172fd2270202dc747a2d288b687c72bc70f654c797bc24a93adb67631128d62461588d7cc070 + languageName: node + linkType: hard + "native-promise-only@npm:^0.8.1": version: 0.8.1 resolution: "native-promise-only@npm:0.8.1" @@ -13431,7 +13515,7 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": +"queue-microtask@npm:1.2.3, queue-microtask@npm:^1.2.2, queue-microtask@npm:^1.2.3": version: 1.2.3 resolution: "queue-microtask@npm:1.2.3" checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b @@ -15120,7 +15204,7 @@ __metadata: ws: "npm:8.13.0" xstate: "npm:4.38.2" peerDependencies: - "@hathor/wallet-lib": 0.39.0 + "@hathor/wallet-lib": 1.14.1 "@wallet-service/common": 1.5.0 languageName: unknown linkType: soft @@ -16195,7 +16279,7 @@ __metadata: webpack-node-externals: "npm:3.0.0" winston: "npm:3.13.0" peerDependencies: - "@hathor/wallet-lib": 0.39.0 + "@hathor/wallet-lib": 1.14.1 "@wallet-service/common": 1.5.0 languageName: unknown linkType: soft @@ -16511,7 +16595,22 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.2.1, ws@npm:^7.5.3, ws@npm:^7.5.9": +"ws@npm:8.17.1": + version: 8.17.1 + resolution: "ws@npm:8.17.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/4264ae92c0b3e59c7e309001e93079b26937aab181835fb7af79f906b22cd33b6196d96556dafb4e985742dd401e99139572242e9847661fdbc96556b9e6902d + languageName: node + linkType: hard + +"ws@npm:^7.5.3, ws@npm:^7.5.9": version: 7.5.9 resolution: "ws@npm:7.5.9" peerDependencies: From c76801fb667d61445a502dd83b4e4bc40de53768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 26 Feb 2025 18:30:07 -0300 Subject: [PATCH 02/17] chore: upgrade daemon --- packages/common/src/types.ts | 1 - packages/common/src/utils/nft.utils.ts | 1 - packages/common/src/utils/wallet.utils.ts | 1 - packages/daemon/src/config.ts | 2 +- packages/daemon/src/services/index.ts | 2 -- packages/daemon/src/types/token.ts | 8 ++++---- packages/daemon/src/utils/wallet.ts | 8 +++----- packages/wallet-service/tests/txProposal.test.ts | 2 +- 8 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 561de83b..b6a72944 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -10,7 +10,6 @@ * https://github.com/HathorNetwork/ops-tools/blob/master/docs/on-call/guide.md#alert-severitypriority */ -// @ts-ignore import { constants } from '@hathor/wallet-lib'; import { isAuthority } from './utils/wallet.utils'; diff --git a/packages/common/src/utils/nft.utils.ts b/packages/common/src/utils/nft.utils.ts index dd87a44d..8d79fade 100644 --- a/packages/common/src/utils/nft.utils.ts +++ b/packages/common/src/utils/nft.utils.ts @@ -8,7 +8,6 @@ import { LambdaClient, InvokeCommand, InvokeCommandOutput } from '@aws-sdk/client-lambda'; import { addAlert } from './alerting.utils'; import { Transaction, Severity } from '../types'; -// @ts-ignore import { Network, constants, CreateTokenTransaction, helpersUtils } from '@hathor/wallet-lib'; import { Logger } from 'winston'; diff --git a/packages/common/src/utils/wallet.utils.ts b/packages/common/src/utils/wallet.utils.ts index 66a4c43b..e0ea9ea9 100644 --- a/packages/common/src/utils/wallet.utils.ts +++ b/packages/common/src/utils/wallet.utils.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -// @ts-ignore import { constants } from '@hathor/wallet-lib'; /** diff --git a/packages/daemon/src/config.ts b/packages/daemon/src/config.ts index 4f6e7814..f0f4bd8d 100644 --- a/packages/daemon/src/config.ts +++ b/packages/daemon/src/config.ts @@ -49,7 +49,7 @@ export const STAGE = process.env.STAGE ?? 'local'; export const FULLNODE_PEER_ID = process.env.FULLNODE_PEER_ID; export const FULLNODE_HOST = process.env.FULLNODE_HOST; export const STREAM_ID = process.env.STREAM_ID; -export const NETWORK = process.env.NETWORK; +export const NETWORK = String(process.env.NETWORK); /* The network name that comes from the fullnode events might be different from * the network we should use to derive addresses, e.g. testnet-golf instead of * testnet diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index 89f11e61..d9735ea6 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -// @ts-ignore import hathorLib from '@hathor/wallet-lib'; import { Connection as MysqlConnection } from 'mysql2/promise'; import axios from 'axios'; @@ -44,7 +43,6 @@ import { validateAddressBalances, getWalletBalancesForTx, getFullnodeHttpUrl, - sendMessageSQS, generateAddresses, sendRealtimeTx, } from '../utils'; diff --git a/packages/daemon/src/types/token.ts b/packages/daemon/src/types/token.ts index e384707e..c53da8a9 100644 --- a/packages/daemon/src/types/token.ts +++ b/packages/daemon/src/types/token.ts @@ -5,8 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -// @ts-ignore -import hathorLib from '@hathor/wallet-lib'; +import { constants } from '@hathor/wallet-lib'; export class TokenInfo { id: string; @@ -23,9 +22,10 @@ export class TokenInfo { this.symbol = symbol; this.transactions = transactions || 0; - const hathorConfig = hathorLib.constants.HATHOR_TOKEN_CONFIG; + // XXX: get config from settings? + const hathorConfig = constants.DEFAULT_NATIVE_TOKEN_CONFIG; - if (this.id === hathorConfig.uid) { + if (this.id === constants.NATIVE_TOKEN_UID) { this.name = hathorConfig.name; this.symbol = hathorConfig.symbol; } diff --git a/packages/daemon/src/utils/wallet.ts b/packages/daemon/src/utils/wallet.ts index 11a2b0bc..2ffd5ede 100644 --- a/packages/daemon/src/utils/wallet.ts +++ b/packages/daemon/src/utils/wallet.ts @@ -5,8 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -// @ts-ignore -import hathorLib, { constants, Output } from '@hathor/wallet-lib'; +import { constants, Output, walletUtils } from '@hathor/wallet-lib'; import { Connection as MysqlConnection } from 'mysql2/promise'; import { strict as assert } from 'assert'; import { @@ -40,8 +39,6 @@ import { updateWalletLockedBalance, } from '../db'; import logger from '../logger'; -// @ts-ignore -import { walletUtils } from '@hathor/wallet-lib'; import { stringMapIterator } from './helpers'; /** @@ -66,6 +63,7 @@ export const prepareOutputs = (outputs: EventTxOutput[], tokens: string[]): TxOu const preparedOutputs: [number, TxOutputWithIndex[]] = outputs.reduce( ([currIndex, newOutputs]: [number, TxOutputWithIndex[]], _output: EventTxOutput): [number, TxOutputWithIndex[]] => { + // XXX: Output typing makes no sense here, maybe we should convert from Output to the wallet-service's own TxOutput const output = new Output(_output.value, Buffer.from(_output.script, 'base64'), { tokenData: _output.token_data, }); @@ -185,7 +183,7 @@ export const unlockUtxos = async (mysql: MysqlConnection, utxos: DbTxOutput[], u decoded, locked: false, // set authority bit if necessary - token_data: utxo.authorities > 0 ? hathorLib.constants.TOKEN_AUTHORITY_MASK : 0, + token_data: utxo.authorities > 0 ? constants.TOKEN_AUTHORITY_MASK : 0, // we don't care about spent_by and script spent_by: null, script: '', diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index c618722e..9ed02cb0 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -26,7 +26,7 @@ import { APIGatewayProxyResult } from 'aws-lambda'; import { ApiError } from '@src/api/errors'; import hathorLib from '@hathor/wallet-lib'; -import CreateTokenTransaction from '@hathor/wallet-lib/lib/models/create_token_transaction'; +import CreateTokenTransaction from '@hathor/wallet-lib'; const defaultDerivationPath = `m/44'/${hathorLib.constants.HATHOR_BIP44_CODE}'/0'/0/`; From e988c22b9684ee68389d1f1b02797ac5d66531d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 26 Feb 2025 18:53:19 -0300 Subject: [PATCH 03/17] chore: remove deprecated getAddresses for deriveAddressFromXPubP2PKH --- packages/daemon/src/utils/wallet.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/daemon/src/utils/wallet.ts b/packages/daemon/src/utils/wallet.ts index 2ffd5ede..edae1e1c 100644 --- a/packages/daemon/src/utils/wallet.ts +++ b/packages/daemon/src/utils/wallet.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { constants, Output, walletUtils } from '@hathor/wallet-lib'; +import { constants, Output, walletUtils, addressUtils } from '@hathor/wallet-lib'; import { Connection as MysqlConnection } from 'mysql2/promise'; import { strict as assert } from 'assert'; import { @@ -529,7 +529,11 @@ export const generateAddresses = async ( // (more details in https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#Change) // so we derive our xpub to this path and use it to get the addresses const derivedXpub = walletUtils.xpubDeriveChild(xpubkey, 0); - const addrMap = walletUtils.getAddresses(derivedXpub, startIndex, count, network); + const addrMap: StringMap = {}; + for (let index = startIndex; index < startIndex + count; index++) { + const address = addressUtils.deriveAddressFromXPubP2PKH(derivedXpub, index, network); + addrMap[address.base58] = index; + } return addrMap; }; From 8b9974aa828c3b93b32edd0f9ff008da3b593ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 26 Feb 2025 22:21:57 -0300 Subject: [PATCH 04/17] chore: upgrade constants used in the wallet-service files --- packages/wallet-service/src/api/tokens.ts | 2 +- .../wallet-service/src/api/totalSupply.ts | 2 +- .../src/api/txProposalCreate.ts | 11 +++++---- packages/wallet-service/src/api/txhistory.ts | 2 +- packages/wallet-service/src/commons.ts | 23 +++++++++++-------- packages/wallet-service/src/types.ts | 4 ++-- packages/wallet-service/src/utils.ts | 10 ++++---- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/wallet-service/src/api/tokens.ts b/packages/wallet-service/src/api/tokens.ts index fa58e565..3bd891aa 100644 --- a/packages/wallet-service/src/api/tokens.ts +++ b/packages/wallet-service/src/api/tokens.ts @@ -68,7 +68,7 @@ export const getTokenDetails = middy(walletIdProxyHandler(async (walletId, event const tokenId = value.token_id; const tokenInfo: TokenInfo = await getTokenInformation(mysql, tokenId); - if (tokenId === constants.HATHOR_TOKEN_CONFIG.uid) { + if (tokenId === constants.NATIVE_TOKEN_UID) { const details = [{ message: 'Invalid tokenId', }]; diff --git a/packages/wallet-service/src/api/totalSupply.ts b/packages/wallet-service/src/api/totalSupply.ts index 283aed53..5698bb2d 100644 --- a/packages/wallet-service/src/api/totalSupply.ts +++ b/packages/wallet-service/src/api/totalSupply.ts @@ -19,7 +19,7 @@ import cors from '@middy/http-cors'; import hathorLib from '@hathor/wallet-lib'; import Joi from 'joi'; -const htrToken = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid; +const htrToken = hathorLib.constants.NATIVE_TOKEN_UID; const mysql = getDbConnection(); const paramsSchema = Joi.object({ tokenId: Joi.string() diff --git a/packages/wallet-service/src/api/txProposalCreate.ts b/packages/wallet-service/src/api/txProposalCreate.ts index 9bf67930..2f6a9f14 100644 --- a/packages/wallet-service/src/api/txProposalCreate.ts +++ b/packages/wallet-service/src/api/txProposalCreate.ts @@ -28,7 +28,7 @@ import { closeDbAndGetError } from '@src/api/utils'; import { closeDbConnection, getDbConnection, getUnixTimestamp } from '@src/utils'; import middy from '@middy/core'; import cors from '@middy/http-cors'; -import hathorLib from '@hathor/wallet-lib'; +import { constants, Network, Transaction, helpersUtils } from '@hathor/wallet-lib'; const mysql = getDbConnection(); @@ -67,9 +67,10 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { } const body = value; - const tx: hathorLib.Transaction = hathorLib.helpersUtils.createTxFromHex(body.txHex, new hathorLib.Network(process.env.NETWORK)); + const tx: Transaction = helpersUtils.createTxFromHex(body.txHex, new Network(process.env.NETWORK)); - if (tx.outputs.length > hathorLib.transaction.getMaxOutputsConstant()) { + // XXX: DEC-0001 + if (tx.outputs.length > constants.MAX_OUTPUTS) { return closeDbAndGetError(mysql, ApiError.TOO_MANY_OUTPUTS, { outputs: tx.outputs.length }); } @@ -110,7 +111,7 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { return closeDbAndGetError(mysql, ApiError.INPUTS_ALREADY_USED); } - if (inputUtxos.length > hathorLib.transaction.getMaxInputsConstant()) { + if (inputUtxos.length > constants.MAX_OUTPUTS) { return closeDbAndGetError(mysql, ApiError.TOO_MANY_INPUTS, { inputs: inputUtxos.length }); } @@ -127,7 +128,7 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { // XXX We should store in address table the path of the address, not the index // For now we return the hardcoded path with only the address index as variable // The client will be prepared to receive any path when we add this in the service in the future - const addressPath = `m/44'/${hathorLib.constants.HATHOR_BIP44_CODE}'/0'/0/${addressDetail.index}`; + const addressPath = `m/44'/${constants.HATHOR_BIP44_CODE}'/0'/0/${addressDetail.index}`; return { txId: utxo.txId, index: utxo.index, addressPath }; }); diff --git a/packages/wallet-service/src/api/txhistory.ts b/packages/wallet-service/src/api/txhistory.ts index 5540b051..1a5cc03c 100644 --- a/packages/wallet-service/src/api/txhistory.ts +++ b/packages/wallet-service/src/api/txhistory.ts @@ -21,7 +21,7 @@ import cors from '@middy/http-cors'; import Joi from 'joi'; const MAX_COUNT = parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10); -const htrToken = hathorLib.constants.HATHOR_TOKEN_CONFIG.uid; +const htrToken = hathorLib.constants.NATIVE_TOKEN_UID; const paramsSchema = Joi.object({ token_id: Joi.string() diff --git a/packages/wallet-service/src/commons.ts b/packages/wallet-service/src/commons.ts index 809ce4cd..b8ab2c7c 100644 --- a/packages/wallet-service/src/commons.ts +++ b/packages/wallet-service/src/commons.ts @@ -305,7 +305,9 @@ export const maybeRefreshWalletConstants = async (mysql: ServerlessMysql): Promi if (!lastVersionData || now - lastVersionData.timestamp > VERSION_CHECK_MAX_DIFF) { // Query and update versions - const apiResponse = await hathorLib.version.checkApiVersion(); + // XXX: DEC-0001 + const apiResponse = await hathorLib.versionApi.asyncGetVersion(); + // const apiResponse = await hathorLib.version.checkApiVersion(); const versionData: FullNodeVersionData = { timestamp: now, version: apiResponse.version, @@ -322,15 +324,16 @@ export const maybeRefreshWalletConstants = async (mysql: ServerlessMysql): Promi await updateVersionData(mysql, versionData); } else { - hathorLib.transaction.updateTransactionWeightConstants( - lastVersionData.minTxWeight, - lastVersionData.minTxWeightCoefficient, - lastVersionData.minTxWeightK, - ); - hathorLib.tokens.updateDepositPercentage(lastVersionData.tokenDepositPercentage); - hathorLib.transaction.updateMaxInputsConstant(lastVersionData.maxNumberInputs); - hathorLib.transaction.updateMaxOutputsConstant(lastVersionData.maxNumberOutputs); - hathorLib.wallet.updateRewardLockConstant(lastVersionData.rewardSpendMinBlocks); + // XXX: DEC-0001 + // hathorLib.transaction.updateTransactionWeightConstants( + // lastVersionData.minTxWeight, + // lastVersionData.minTxWeightCoefficient, + // lastVersionData.minTxWeightK, + // ); + // hathorLib.tokens.updateDepositPercentage(lastVersionData.tokenDepositPercentage); + // hathorLib.transaction.updateMaxInputsConstant(lastVersionData.maxNumberInputs); + // hathorLib.transaction.updateMaxOutputsConstant(lastVersionData.maxNumberOutputs); + // hathorLib.wallet.updateRewardLockConstant(lastVersionData.rewardSpendMinBlocks); } }; diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index dde54bd7..1f911035 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -111,9 +111,9 @@ export class TokenInfo { this.symbol = symbol; this.transactions = transactions || 0; - const hathorConfig = hathorLib.constants.HATHOR_TOKEN_CONFIG; + const hathorConfig = hathorLib.constants.DEFAULT_NATIVE_TOKEN_CONFIG; - if (this.id === hathorConfig.uid) { + if (this.id === hathorLib.constants.NATIVE_TOKEN_UID) { this.name = hathorConfig.name; this.symbol = hathorConfig.symbol; } diff --git a/packages/wallet-service/src/utils.ts b/packages/wallet-service/src/utils.ts index dea16943..6a740a88 100644 --- a/packages/wallet-service/src/utils.ts +++ b/packages/wallet-service/src/utils.ts @@ -18,6 +18,7 @@ import BIP32Factory from 'bip32'; const bip32 = BIP32Factory(ecc); +// XXX - Check effects of this storage /* TODO: We should remove this as soon as the wallet-lib is refactored * (https://github.com/HathorNetwork/hathor-wallet-lib/issues/122) */ @@ -49,20 +50,21 @@ export class CustomStorage { } preStart(): void { + // XXX: DEC-0002 + // DEFAULT_SERVER - 'https://node1.mainnet.hathor.network/v1a/' this.store = { - 'wallet:server': process.env.DEFAULT_SERVER || hathorLib.constants.DEFAULT_SERVER, - 'wallet:defaultServer': process.env.DEFAULT_SERVER || hathorLib.constants.DEFAULT_SERVER, + 'wallet:server': process.env.DEFAULT_SERVER || 'https://node1.mainnet.hathor.network/v1a/', + 'wallet:defaultServer': process.env.DEFAULT_SERVER || 'https://node1.mainnet.hathor.network/v1a/', }; } } hathorLib.network.setNetwork(process.env.NETWORK); -hathorLib.storage.setStore(new CustomStorage()); const libNetwork = hathorLib.network.getNetwork(); const hathorNetwork = { messagePrefix: '\x18Hathor Signed Message:\n', - bech32: hathorLib.network.bech32prefix, + bech32: libNetwork.bech32prefix, bip32: { public: libNetwork.xpubkey, private: libNetwork.xprivkey, From aa29132f314aa97ce6dd68edadf823d4987e2bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 26 Feb 2025 23:43:16 -0300 Subject: [PATCH 05/17] chore: update nft utils tests --- packages/common/__tests__/utils/nft.utils.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/common/__tests__/utils/nft.utils.test.ts b/packages/common/__tests__/utils/nft.utils.test.ts index c678bdb4..3a27162f 100644 --- a/packages/common/__tests__/utils/nft.utils.test.ts +++ b/packages/common/__tests__/utils/nft.utils.test.ts @@ -1,4 +1,3 @@ -// @ts-ignore: Using old wallet-lib version, no types exported import hathorLib from '@hathor/wallet-lib'; import { mockedAddAlert } from './alerting.utils.mock'; import { NftUtils } from '@src/utils/nft.utils'; @@ -96,7 +95,6 @@ describe('isTransactionNFTCreation', () => { // Preparing mocks const spyCreateTx = jest.spyOn(hathorLib.helpersUtils, 'createTxFromHistoryObject'); - spyCreateTx.mockImplementation(() => ({})); let tx; let result; @@ -265,7 +263,7 @@ describe('_updateMetadata', () => { }); // eslint-disable-next-line jest/valid-expect - expect(NftUtils._updateMetadata('sampleUid', { sampleData: 'fake' }, network, logger)) + expect(NftUtils._updateMetadata('sampleUid', { sampleData: 'fake' }, 3, logger)) .rejects.toThrow(new Error('Metadata update failed for tx_id: sampleUid.')); }); }); From 80e40be04053a6c18e316c53fac36cf4ff7e509d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 27 Feb 2025 13:41:12 -0300 Subject: [PATCH 06/17] chore: update tests to use most recent utils and constants --- packages/daemon/__tests__/db/index.test.ts | 1 - packages/wallet-service/tests/api.test.ts | 26 +++--- packages/wallet-service/tests/commons.test.ts | 88 ++++++++++--------- .../wallet-service/tests/txProposal.test.ts | 9 +- packages/wallet-service/tests/utils.test.ts | 15 ++-- packages/wallet-service/tests/utils.ts | 5 +- 6 files changed, 77 insertions(+), 67 deletions(-) diff --git a/packages/daemon/__tests__/db/index.test.ts b/packages/daemon/__tests__/db/index.test.ts index 930ed492..d61fc123 100644 --- a/packages/daemon/__tests__/db/index.test.ts +++ b/packages/daemon/__tests__/db/index.test.ts @@ -71,7 +71,6 @@ import { import { isAuthority } from '@wallet-service/common'; import { DbTxOutput, StringMap, TokenInfo, WalletStatus } from '../../src/types'; import { Authorities, TokenBalanceMap } from '@wallet-service/common'; -// @ts-ignore import { constants } from '@hathor/wallet-lib'; import { generateAddresses } from '../../src/utils'; diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 4290358a..7d306c90 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -30,7 +30,7 @@ import { ApiError } from '@src/api/errors'; import { closeDbConnection, getDbConnection, getUnixTimestamp, getWalletId } from '@src/utils'; import { STATUS_CODE_TABLE } from '@src/api/utils'; import { WalletStatus, FullNodeVersionData } from '@src/types'; -import { walletUtils, constants, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; +import { walletUtils, addressUtils, constants, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; import bitcore from 'bitcore-lib'; import { ADDRESSES, @@ -793,7 +793,8 @@ test('POST /wallet', async () => { // get the first address const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); - const firstAddress = walletUtils.getAddressAtIndex(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; // Wrong first address event = makeGatewayEvent({}, JSON.stringify({ @@ -926,7 +927,8 @@ test('POST /wallet should fail with ApiError.WALLET_MAX_RETRIES when max retries // get the first address const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); - const firstAddress = walletUtils.getAddressAtIndex(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; // we need signatures for both the account path and the purpose path: const now = Math.floor(Date.now() / 1000); @@ -1157,7 +1159,8 @@ test('PUT /wallet/auth should change the auth_xpub only after validating both th // get the first address const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); - const firstAddress = walletUtils.getAddressAtIndex(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; // we need signatures for both the account path and the purpose path: const now = Math.floor(Date.now() / 1000); @@ -1181,7 +1184,8 @@ test('loadWallet API should fail if a wrong signature is sent', async () => { expect.hasAssertions(); const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); - const firstAddress = walletUtils.getAddressAtIndex(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; const now = Math.floor(Date.now() / 1000); const walletId = getWalletId(XPUBKEY); @@ -1522,7 +1526,7 @@ test('GET /wallet/tokens/token_id/details', async () => { expect(returnBody.details.authorities.melt).toStrictEqual(true); expect(returnBody.details.tokenInfo).toStrictEqual(token2); - event = makeGatewayEventWithAuthorizer('my-wallet', { token_id: constants.HATHOR_TOKEN_CONFIG.uid }); + event = makeGatewayEventWithAuthorizer('my-wallet', { token_id: constants.NATIVE_TOKEN_UID }); result = await getTokenDetails(event, null, null) as APIGatewayProxyResult; returnBody = JSON.parse(result.body as string); @@ -1539,11 +1543,12 @@ test('GET /wallet/tokens/token_id/details', async () => { ] `); - const oldHathorTokenConfig = constants.HATHOR_TOKEN_CONFIG.uid; + const oldHathorTokenConfig = constants.NATIVE_TOKEN_UID; - constants.HATHOR_TOKEN_CONFIG.uid = TX_IDS[4]; + // @ts-ignore + constants.NATIVE_TOKEN_UID = TX_IDS[4]; - event = makeGatewayEventWithAuthorizer('my-wallet', { token_id: constants.HATHOR_TOKEN_CONFIG.uid }); + event = makeGatewayEventWithAuthorizer('my-wallet', { token_id: constants.NATIVE_TOKEN_UID }); result = await getTokenDetails(event, null, null) as APIGatewayProxyResult; returnBody = JSON.parse(result.body as string); @@ -1551,7 +1556,8 @@ test('GET /wallet/tokens/token_id/details', async () => { expect(returnBody.success).toBe(false); expect(returnBody.details).toStrictEqual([{ message: 'Invalid tokenId' }]); - constants.HATHOR_TOKEN_CONFIG.uid = oldHathorTokenConfig; + // @ts-ignore + constants.NATIVE_TOKEN_UID = oldHathorTokenConfig; }); test('GET /wallet/utxos', async () => { diff --git a/packages/wallet-service/tests/commons.test.ts b/packages/wallet-service/tests/commons.test.ts index b385b418..8079cef9 100644 --- a/packages/wallet-service/tests/commons.test.ts +++ b/packages/wallet-service/tests/commons.test.ts @@ -510,7 +510,8 @@ test('unlockTimelockedUtxos', async () => { await expect(checkWalletBalanceTable(mysql, 1, walletId, token, 5000, 0, null, 3, 0b10, 0)).resolves.toBe(true); }); -test('maybeRefreshWalletConstants with an uninitialized version_data database should call hathorLib.version.checkApiVersion()', async () => { +// XXX: DEC-0001 +test('maybeRefreshWalletConstants with an uninitialized version_data database should call hathorLib.versionApi.asyncGetVersion()', async () => { expect.hasAssertions(); const spy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); @@ -545,48 +546,49 @@ test('maybeRefreshWalletConstants with an uninitialized version_data database sh expect(mockGet).toHaveBeenCalledTimes(1); }); -test('maybeRefreshWalletConstants with an initialized version_data database should query data from the database', async () => { - expect.hasAssertions(); - - const axiosSpy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); - const mockGet = jest.fn(() => Promise.resolve({ data: {} })); - - axiosSpy.mockReturnValue({ get: mockGet }); - - const mockedVersionData: FullNodeVersionData = { - timestamp: new Date().getTime(), - version: '0.38.0', - network: 'mainnet', - minWeight: 14, - minTxWeight: 14, - minTxWeightCoefficient: 1.6, - minTxWeightK: 100, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 255, - }; - - await updateVersionData(mysql, mockedVersionData); - - await maybeRefreshWalletConstants(mysql); - - const { - txMinWeight, - txWeightCoefficient, - txMinWeightK, - } = hathorLib.transaction.getTransactionWeightConstants(); - - const maxNumberInputs = hathorLib.transaction.getMaxInputsConstant(); - const maxNumberOutputs = hathorLib.transaction.getMaxOutputsConstant(); - - expect(mockGet).toHaveBeenCalledTimes(0); - expect(txMinWeight).toStrictEqual(mockedVersionData.minTxWeight); - expect(txWeightCoefficient).toStrictEqual(mockedVersionData.minTxWeightCoefficient); - expect(txMinWeightK).toStrictEqual(mockedVersionData.minTxWeightK); - expect(maxNumberInputs).toStrictEqual(mockedVersionData.maxNumberInputs); - expect(maxNumberOutputs).toStrictEqual(mockedVersionData.maxNumberOutputs); -}); +// XXX: DEC-0001 +// test('maybeRefreshWalletConstants with an initialized version_data database should query data from the database', async () => { +// expect.hasAssertions(); + +// const axiosSpy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); +// const mockGet = jest.fn(() => Promise.resolve({ data: {} })); + +// axiosSpy.mockReturnValue({ get: mockGet }); + +// const mockedVersionData: FullNodeVersionData = { +// timestamp: new Date().getTime(), +// version: '0.38.0', +// network: 'mainnet', +// minWeight: 14, +// minTxWeight: 14, +// minTxWeightCoefficient: 1.6, +// minTxWeightK: 100, +// tokenDepositPercentage: 0.01, +// rewardSpendMinBlocks: 300, +// maxNumberInputs: 255, +// maxNumberOutputs: 255, +// }; + +// await updateVersionData(mysql, mockedVersionData); + +// await maybeRefreshWalletConstants(mysql); + +// const { +// txMinWeight, +// txWeightCoefficient, +// txMinWeightK, +// } = hathorLib.transaction.getTransactionWeightConstants(); + +// const maxNumberInputs = hathorLib.transaction.getMaxInputsConstant(); +// const maxNumberOutputs = hathorLib.transaction.getMaxOutputsConstant(); + +// expect(mockGet).toHaveBeenCalledTimes(0); +// expect(txMinWeight).toStrictEqual(mockedVersionData.minTxWeight); +// expect(txWeightCoefficient).toStrictEqual(mockedVersionData.minTxWeightCoefficient); +// expect(txMinWeightK).toStrictEqual(mockedVersionData.minTxWeightK); +// expect(maxNumberInputs).toStrictEqual(mockedVersionData.maxNumberInputs); +// expect(maxNumberOutputs).toStrictEqual(mockedVersionData.maxNumberOutputs); +// }); test('searchForLatestValidBlock should find the first voided block', async () => { expect.hasAssertions(); diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index 9ed02cb0..00ab4871 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -25,8 +25,7 @@ import { APIGatewayProxyResult } from 'aws-lambda'; import { ApiError } from '@src/api/errors'; -import hathorLib from '@hathor/wallet-lib'; -import CreateTokenTransaction from '@hathor/wallet-lib'; +import hathorLib, { CreateTokenTransaction } from '@hathor/wallet-lib'; const defaultDerivationPath = `m/44'/${hathorLib.constants.HATHOR_BIP44_CODE}'/0'/0/`; @@ -236,7 +235,7 @@ test('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY const token1 = '004d75c1edd4294379e7e5b7ab6c118c53c8b07a506728feb5688c8d26a97e50'; const token2 = '002f2bcc3261b4fb8510a458ed9df9f6ba2a413ee35901b3c5f81b0c085287e2'; - const utxos = [ + const utxos: [string, number, string, string, number, number, null, null, boolean][] = [ ['004d75c1edd4294379e7e5b7ab6c118c53c8b07a506728feb5688c8d26a97e50', 0, token1, ADDRESSES[0], 300, 0, null, null, false], ['0000001e39bc37fe8710c01cc1e8c0a937bf6f9337551fbbfddc222bfc28c197', 0, token1, ADDRESSES[0], 100, 0, null, null, false], ['00000060a25077e48926bcd9473d77259296e123ec6af1c1a16c1c381093ab90', 0, token2, ADDRESSES[0], 300, 0, null, null, false], @@ -1510,9 +1509,7 @@ test('POST /txproposals a tx create action on txHex', async () => { const name = 'Test token'; const symbol = 'TSTKN'; - const transaction = new CreateTokenTransaction(name, symbol, inputs, outputs, { - version: hathorLib.constants.CREATE_TOKEN_TX_VERSION, - }); + const transaction = new CreateTokenTransaction(name, symbol, inputs, outputs); const txHex = transaction.toHex(); const event = makeGatewayEventWithAuthorizer('my-wallet', null, JSON.stringify({ txHex })); diff --git a/packages/wallet-service/tests/utils.test.ts b/packages/wallet-service/tests/utils.test.ts index c3568748..eaa11379 100644 --- a/packages/wallet-service/tests/utils.test.ts +++ b/packages/wallet-service/tests/utils.test.ts @@ -3,13 +3,14 @@ import hathorLib from '@hathor/wallet-lib'; import * as Fullnode from '@src/fullnode'; import { TEST_SEED, XPUBKEY, AUTH_XPUBKEY, ADDRESSES } from '@tests/utils'; +// XXX: DEC-0002 test('CustomStorage', () => { expect.hasAssertions(); const store = new CustomStorage(); // Should be initialized with hathor default server and server - expect(store.getItem('wallet:defaultServer')).toBe(hathorLib.constants.DEFAULT_SERVER); - expect(store.getItem('wallet:server')).toBe(hathorLib.constants.DEFAULT_SERVER); + expect(store.getItem('wallet:defaultServer')).toBe('https://node1.mainnet.hathor.network/v1a/'); + expect(store.getItem('wallet:server')).toBe('https://node1.mainnet.hathor.network/v1a/'); store.setItem('hathor', 'hathor'); expect(store.getItem('hathor')).toBe('hathor'); @@ -22,8 +23,8 @@ test('CustomStorage', () => { expect(store.getItem('hathor')).toBeUndefined(); store.preStart(); - expect(store.getItem('wallet:defaultServer')).toBe(hathorLib.constants.DEFAULT_SERVER); - expect(store.getItem('wallet:server')).toBe(hathorLib.constants.DEFAULT_SERVER); + expect(store.getItem('wallet:defaultServer')).toBe('https://node1.mainnet.hathor.network/v1a/'); + expect(store.getItem('wallet:server')).toBe('https://node1.mainnet.hathor.network/v1a/'); }); test('sha256d', () => { @@ -89,6 +90,10 @@ test('XPUBKEY, AUTH_XPUBKEY and ADDRESSES should be derived from TEST_SEED', asy // Generate addresses in change derivation path 0 const derivedXpub = hathorLib.walletUtils.xpubDeriveChild(xpubkey, 0); - const addresses = Object.keys(hathorLib.walletUtils.getAddresses(derivedXpub, 0, 17)); + const addresses: string[] = []; + for (let index = 0; index < 17; index++) { + const addressInfo = hathorLib.addressUtils.deriveAddressFromXPubP2PKH(derivedXpub, index, 'mainnet'); + addresses.push(addressInfo.base58); + } expect(addresses).toStrictEqual(ADDRESSES); }); diff --git a/packages/wallet-service/tests/utils.ts b/packages/wallet-service/tests/utils.ts index b1cc0b47..e07e08b7 100644 --- a/packages/wallet-service/tests/utils.ts +++ b/packages/wallet-service/tests/utils.ts @@ -12,7 +12,7 @@ import { } from '@src/types'; import { TxInput } from '@wallet-service/common/src/types'; import { getWalletId } from '@src/utils'; -import { walletUtils, Network, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; +import { addressUtils, walletUtils, Network, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; import { AddressTxHistoryTableEntry, AddressTableEntry, @@ -920,7 +920,8 @@ export const redisCleanup = ( export const getAuthData = (now: number): any => { // get the first address const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); - const firstAddress = walletUtils.getAddressAtIndex(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; // we need signatures for both the account path and the purpose path: const walletId = getWalletId(XPUBKEY); From 380cdf14df976396aa0a8aad30735fe3680be6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 27 Feb 2025 13:55:07 -0300 Subject: [PATCH 07/17] chore: ignore type issues with the mocked apis --- packages/wallet-service/tests/commons.test.ts | 2 ++ packages/wallet-service/tests/txProposal.test.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/packages/wallet-service/tests/commons.test.ts b/packages/wallet-service/tests/commons.test.ts index 8079cef9..c4fed141 100644 --- a/packages/wallet-service/tests/commons.test.ts +++ b/packages/wallet-service/tests/commons.test.ts @@ -533,11 +533,13 @@ test('maybeRefreshWalletConstants with an uninitialized version_data database sh })); spy.mockReturnValue({ + // @ts-ignore post: () => Promise.resolve({ data: { success: true, }, }), + // @ts-ignore get: mockGet, }); diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index 00ab4871..7e00c42a 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -747,12 +747,14 @@ test('PUT /txproposals/{proposalId} with an invalid txHex should fail and update // Create the spy to mock wallet-lib const spy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); spy.mockReturnValue({ + // @ts-ignore post: () => Promise.resolve({ data: { success: false, message: 'invalid txhex', }, }), + // @ts-ignore get: () => Promise.resolve({ data: { success: true, @@ -896,6 +898,7 @@ test('PUT /txproposals/{proposalId} should update tx_proposal to SEND_ERROR on f post: () => { throw new Error('Wallet lib error'); }, + // @ts-ignore get: () => Promise.resolve({ data: { success: true, @@ -1529,9 +1532,11 @@ test('PUT /txproposals/{proposalId} with txhex', async () => { // Create the spy to mock wallet-lib const spy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); spy.mockReturnValue({ + // @ts-ignore post: () => Promise.resolve({ data: { success: true }, }), + // @ts-ignore get: () => Promise.resolve({ data: { success: true, @@ -1669,9 +1674,11 @@ test('PUT /txproposals/{proposalId} with a different txhex than the one sent in // Create the spy to mock wallet-lib const spy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); spy.mockReturnValue({ + // @ts-ignore post: () => Promise.resolve({ data: { success: true }, }), + // @ts-ignore get: () => Promise.resolve({ data: { success: true, From 9a3bb63f49ffbfc81197f7d2937a03f12d4032c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 27 Feb 2025 14:08:05 -0300 Subject: [PATCH 08/17] chore: skip failing test due to DEC-0001 --- packages/wallet-service/tests/txProposal.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index 7e00c42a..7d0299ab 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -197,7 +197,8 @@ test('POST /txproposals with utxos that are already used on another txproposal s expect(usedInputsReturnBody.error).toBe(ApiError.INPUTS_ALREADY_USED); }); -test('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY_OUTPUTS', async () => { +// XXX: DEC-0001 +test.skip('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY_OUTPUTS', async () => { expect.hasAssertions(); const now = getUnixTimestamp(); From 3142f9abfdceec55a59e49155fc293be5394a969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 12 Mar 2025 22:44:29 -0300 Subject: [PATCH 09/17] Feat/node config module (#217) * feat: config modules * refactor: Use new config modules --- .github/workflows/main.yml | 5 +- .../20250306170811-node_version_proxy.js | 83 ++++++++++++ packages/wallet-service/README.md | 2 +- packages/wallet-service/package.json | 1 + packages/wallet-service/src/api/auth.ts | 5 +- .../wallet-service/src/api/healthcheck.ts | 9 +- .../src/api/txProposalCreate.ts | 13 +- .../wallet-service/src/api/txProposalSend.ts | 3 +- packages/wallet-service/src/api/txhistory.ts | 3 +- packages/wallet-service/src/api/version.ts | 12 +- packages/wallet-service/src/api/wallet.ts | 16 ++- packages/wallet-service/src/commons.ts | 55 +------- packages/wallet-service/src/config.ts | 76 +++++++++++ packages/wallet-service/src/db/index.ts | 51 +++----- packages/wallet-service/src/fullnode.ts | 22 +++- packages/wallet-service/src/logger.ts | 3 +- packages/wallet-service/src/mempool.ts | 5 +- packages/wallet-service/src/metrics.ts | 5 +- packages/wallet-service/src/nodeConfig.ts | 53 ++++++++ packages/wallet-service/src/redis.ts | 5 +- packages/wallet-service/src/schemas.ts | 72 +++++++++++ packages/wallet-service/src/txProcessor.ts | 3 +- packages/wallet-service/src/types.ts | 71 +++++++++- packages/wallet-service/src/utils.ts | 56 ++------ .../src/utils/pushnotification.utils.ts | 34 ++--- packages/wallet-service/src/ws/utils.ts | 5 +- packages/wallet-service/tests/api.test.ts | 104 +++++++++++++-- packages/wallet-service/tests/commons.test.ts | 122 ++++++++---------- packages/wallet-service/tests/db.test.ts | 80 +++++++----- packages/wallet-service/tests/env.test.ts | 25 ++++ .../wallet-service/tests/txProposal.test.ts | 54 ++++---- packages/wallet-service/tests/utils.test.ts | 27 +--- packages/wallet-service/tests/utils.ts | 77 +++++------ .../utils/pushnotification.utils.test.ts | 25 +++- .../wallet-service/tests/ws.utils.test.ts | 53 +++++--- yarn.lock | 31 ++++- 36 files changed, 852 insertions(+), 414 deletions(-) create mode 100644 db/migrations/20250306170811-node_version_proxy.js create mode 100644 packages/wallet-service/src/config.ts create mode 100644 packages/wallet-service/src/nodeConfig.ts create mode 100644 packages/wallet-service/src/schemas.ts create mode 100644 packages/wallet-service/tests/env.test.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1cac9ac3..11385fd0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -98,7 +98,7 @@ jobs: DEFAULT_SERVER: https://node1.mainnet.hathor.network/v1a/ VOIDED_TX_OFFSET: 5 WS_DOMAIN: ws.wallet-service.hathor.network - AUTH_SECRET: "" + AUTH_SECRET: "foobar" WALLET_SERVICE_LAMBDA_ENDPOINT: "" FIREBASE_PROJECT_ID: "" FIREBASE_PRIVATE_KEY_ID: "" @@ -114,6 +114,9 @@ jobs: ALERT_MANAGER_REGION: us-east-1 ALERT_MANAGER_TOPIC: alert-topic PUSH_ALLOWED_PROVIDERS: "" + REDIS_URL: redis://127.0.0.1:6379 + REDIS_PASSWORD: "" + IS_OFFLINE: 'true' - name: Run integration tests on the daemon run: | diff --git a/db/migrations/20250306170811-node_version_proxy.js b/db/migrations/20250306170811-node_version_proxy.js new file mode 100644 index 00000000..14cba16a --- /dev/null +++ b/db/migrations/20250306170811-node_version_proxy.js @@ -0,0 +1,83 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up (queryInterface, Sequelize) { + // Dropping the table and recreating it is easier than deleting the fields then addind the new + // one, there is also no danger of losing any data since this is fetched from the fullnode + await queryInterface.dropTable('version_data'); + await queryInterface.createTable('version_data', { + id: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + primaryKey: true, + defaultValue: 1, + }, + timestamp: { + type: Sequelize.BIGINT.UNSIGNED, + allowNull: false, + }, + data: { + type: Sequelize.TEXT, + allowNull: false, + }, + }); + }, + + async down (queryInterface, Sequelize) { + await queryInterface.dropTable('version_data'); + // The "old" table structure was copied from ./20210706175820-create-version-data.js + await queryInterface.createTable('version_data', { + id: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + primaryKey: true, + defaultValue: 1, + }, + timestamp: { + type: Sequelize.BIGINT.UNSIGNED, + allowNull: false, + }, + version: { + type: Sequelize.STRING(11), + allowNull: false, + }, + network: { + type: Sequelize.STRING(8), + allowNull: false, + }, + min_weight: { + type: Sequelize.FLOAT.UNSIGNED, + allowNull: false, + }, + min_tx_weight: { + type: Sequelize.FLOAT.UNSIGNED, + allowNull: false, + }, + min_tx_weight_coefficient: { + type: Sequelize.FLOAT.UNSIGNED, + allowNull: false, + }, + min_tx_weight_k: { + type: Sequelize.FLOAT.UNSIGNED, + allowNull: false, + }, + token_deposit_percentage: { + type: Sequelize.FLOAT.UNSIGNED, + allowNull: false, + }, + reward_spend_min_blocks: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + }, + max_number_inputs: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + }, + max_number_outputs: { + type: Sequelize.INTEGER.UNSIGNED, + allowNull: false, + }, + }); + } +}; diff --git a/packages/wallet-service/README.md b/packages/wallet-service/README.md index a185cc40..ba29aa41 100644 --- a/packages/wallet-service/README.md +++ b/packages/wallet-service/README.md @@ -58,7 +58,7 @@ DB_ENDPOINT=localhost DB_NAME=wallet_service DB_USER=my_user DB_PASS=password123 -REDIS_HOST=localhost +REDIS_URL=localhost REDIS_PORT=6379 AUTH_SECRET=foobar EXPLORER_SERVICE_LAMBDA_ENDPOINT=http://localhost:3001 diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index 264412c2..a1f296f5 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -46,6 +46,7 @@ "devDependencies": { "@types/aws-lambda": "8.10.95", "@types/jest": "^29.5.13", + "@types/joi": "^17.2.3", "@types/node": "18.0.4", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "3.3.0", diff --git a/packages/wallet-service/src/api/auth.ts b/packages/wallet-service/src/api/auth.ts index 22c4463d..cb9baeb0 100644 --- a/packages/wallet-service/src/api/auth.ts +++ b/packages/wallet-service/src/api/auth.ts @@ -30,6 +30,7 @@ import middy from '@middy/core'; import cors from '@middy/http-cors'; import createDefaultLogger from '@src/logger'; import { Logger } from 'winston'; +import config from '@src/config'; const EXPIRATION_TIME_IN_SECONDS = 1800; @@ -141,7 +142,7 @@ export const tokenHandler: APIGatewayProxyHandler = middy(async (event) => { addr: address.toString(), wid: walletId, }, - process.env.AUTH_SECRET, + config.authSecret, { expiresIn: EXPIRATION_TIME_IN_SECONDS, jwtid: uuid4(), @@ -200,7 +201,7 @@ export const bearerAuthorizer: APIGatewayTokenAuthorizerHandler = middy(async (e try { data = jwt.verify( sanitizedToken, - process.env.AUTH_SECRET, + config.authSecret, ); } catch (e) { // XXX: find a way to return specific error to frontend or make all errors Unauthorized? diff --git a/packages/wallet-service/src/api/healthcheck.ts b/packages/wallet-service/src/api/healthcheck.ts index ead57fad..babad766 100644 --- a/packages/wallet-service/src/api/healthcheck.ts +++ b/packages/wallet-service/src/api/healthcheck.ts @@ -12,11 +12,10 @@ import fullnode from '@src/fullnode'; import { closeDbConnection, getDbConnection } from '@src/utils'; import { APIGatewayProxyHandler } from 'aws-lambda'; import { getRedisClient, ping } from '@src/redis'; +import config from '@src/config'; const mysql = getDbConnection(); -const HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE = Number(process.env.HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE ?? 5) - const checkDatabaseHeight: HealthcheckCallbackResponse = async () => { try { const [currentHeight, fullnodeStatus] = await Promise.all([ @@ -26,10 +25,10 @@ const checkDatabaseHeight: HealthcheckCallbackResponse = async () => { const currentFullnodeHeight = fullnodeStatus['dag']['best_block']['height']; - if (currentFullnodeHeight - currentHeight < HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE) { + if (currentFullnodeHeight - currentHeight < config.healthCheckMaximumHeightDifference) { return new HealthcheckCallbackResponse({ status: HealthcheckStatus.PASS, - output: `Database and fullnode heights are within ${HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE} blocks difference`, + output: `Database and fullnode heights are within ${config.healthCheckMaximumHeightDifference} blocks difference`, }); } else { return new HealthcheckCallbackResponse({ @@ -142,4 +141,4 @@ export const getHealthcheck: APIGatewayProxyHandler = middy(async (event) => { statusCode: response.getHttpStatusCode(), body: response.toJson(), }; -}); \ No newline at end of file +}); diff --git a/packages/wallet-service/src/api/txProposalCreate.ts b/packages/wallet-service/src/api/txProposalCreate.ts index 2f6a9f14..8b40abeb 100644 --- a/packages/wallet-service/src/api/txProposalCreate.ts +++ b/packages/wallet-service/src/api/txProposalCreate.ts @@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid'; import Joi from 'joi'; import { ApiError } from '@src/api/errors'; -import { maybeRefreshWalletConstants, walletIdProxyHandler } from '@src/commons'; +import { walletIdProxyHandler } from '@src/commons'; import { createTxProposal, getUtxos, @@ -29,6 +29,8 @@ import { closeDbConnection, getDbConnection, getUnixTimestamp } from '@src/utils import middy from '@middy/core'; import cors from '@middy/http-cors'; import { constants, Network, Transaction, helpersUtils } from '@hathor/wallet-lib'; +import { getFullnodeData } from '@src/nodeConfig'; +import config from '@src/config'; const mysql = getDbConnection(); @@ -42,7 +44,7 @@ const bodySchema = Joi.object({ * This lambda is called by API Gateway on POST /txproposals */ export const create = middy(walletIdProxyHandler(async (walletId, event) => { - await maybeRefreshWalletConstants(mysql); + const versionData = await getFullnodeData(mysql); const eventBody = (function parseBody(body) { try { @@ -67,10 +69,9 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { } const body = value; - const tx: Transaction = helpersUtils.createTxFromHex(body.txHex, new Network(process.env.NETWORK)); + const tx: Transaction = helpersUtils.createTxFromHex(body.txHex, new Network(config.network)); - // XXX: DEC-0001 - if (tx.outputs.length > constants.MAX_OUTPUTS) { + if (tx.outputs.length > versionData.maxNumberOutputs) { return closeDbAndGetError(mysql, ApiError.TOO_MANY_OUTPUTS, { outputs: tx.outputs.length }); } @@ -111,7 +112,7 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { return closeDbAndGetError(mysql, ApiError.INPUTS_ALREADY_USED); } - if (inputUtxos.length > constants.MAX_OUTPUTS) { + if (inputUtxos.length > versionData.maxNumberOutputs) { return closeDbAndGetError(mysql, ApiError.TOO_MANY_INPUTS, { inputs: inputUtxos.length }); } diff --git a/packages/wallet-service/src/api/txProposalSend.ts b/packages/wallet-service/src/api/txProposalSend.ts index fcd42f38..8925dd98 100644 --- a/packages/wallet-service/src/api/txProposalSend.ts +++ b/packages/wallet-service/src/api/txProposalSend.ts @@ -28,6 +28,7 @@ import { import { closeDbAndGetError } from '@src/api/utils'; import middy from '@middy/core'; import cors from '@middy/http-cors'; +import config from '@src/config'; const mysql = getDbConnection(); @@ -91,7 +92,7 @@ export const send: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (wa const now = getUnixTimestamp(); const txProposalInputs = await getTxProposalInputs(mysql, txProposalId); - const tx = hathorLib.helpersUtils.createTxFromHex(txHex, new hathorLib.Network(process.env.NETWORK)); + const tx = hathorLib.helpersUtils.createTxFromHex(txHex, new hathorLib.Network(config.network)); if (tx.inputs.length !== txProposalInputs.length) { return closeDbAndGetError(mysql, ApiError.TX_PROPOSAL_NO_MATCH); diff --git a/packages/wallet-service/src/api/txhistory.ts b/packages/wallet-service/src/api/txhistory.ts index 1a5cc03c..ddeebae5 100644 --- a/packages/wallet-service/src/api/txhistory.ts +++ b/packages/wallet-service/src/api/txhistory.ts @@ -19,8 +19,9 @@ import { walletIdProxyHandler } from '@src/commons'; import middy from '@middy/core'; import cors from '@middy/http-cors'; import Joi from 'joi'; +import config from '@src/config'; -const MAX_COUNT = parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10); +const MAX_COUNT = config.txHistoryMaxCount; const htrToken = hathorLib.constants.NATIVE_TOKEN_UID; const paramsSchema = Joi.object({ diff --git a/packages/wallet-service/src/api/version.ts b/packages/wallet-service/src/api/version.ts index 64fc00f7..342c5580 100644 --- a/packages/wallet-service/src/api/version.ts +++ b/packages/wallet-service/src/api/version.ts @@ -8,18 +8,12 @@ import { APIGatewayProxyHandler } from 'aws-lambda'; import 'source-map-support/register'; -import { - getVersionData, -} from '@src/db'; -import { - FullNodeVersionData, -} from '@src/types'; import { closeDbConnection, getDbConnection, } from '@src/utils'; import { warmupMiddleware } from '@src/api/utils'; -import { maybeRefreshWalletConstants } from '@src/commons'; +import { getRawFullnodeData } from '@src/nodeConfig' import middy from '@middy/core'; import cors from '@middy/http-cors'; @@ -31,9 +25,7 @@ const mysql = getDbConnection(); * This lambda is called by API Gateway on GET /version */ export const get: APIGatewayProxyHandler = middy(async () => { - await maybeRefreshWalletConstants(mysql); - - const versionData: FullNodeVersionData = await getVersionData(mysql); + const versionData = await getRawFullnodeData(mysql); await closeDbConnection(mysql); diff --git a/packages/wallet-service/src/api/wallet.ts b/packages/wallet-service/src/api/wallet.ts index d709e7d3..96cdc938 100644 --- a/packages/wallet-service/src/api/wallet.ts +++ b/packages/wallet-service/src/api/wallet.ts @@ -40,10 +40,11 @@ import Joi from 'joi'; import createDefaultLogger from '@src/logger'; import { Severity } from '@wallet-service/common/src/types'; import { addAlert } from '@wallet-service/common/src/utils/alerting.utils'; +import config from '@src/config'; const mysql = getDbConnection(); -const MAX_LOAD_WALLET_RETRIES: number = parseInt(process.env.MAX_LOAD_WALLET_RETRIES || '5', 10); +const MAX_LOAD_WALLET_RETRIES: number = config.maxLoadWalletRetries; /* * Get the status of a wallet @@ -67,7 +68,7 @@ export const get: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (wal // If the env requires to validate the first address // then we must set the firstAddress field as required -const shouldConfirmFirstAddress = process.env.CONFIRM_FIRST_ADDRESS === 'true'; +const shouldConfirmFirstAddress = config.confirmFirstAddress; const firstAddressJoi = shouldConfirmFirstAddress ? Joi.string().required() : Joi.string(); const loadBodySchema = Joi.object({ @@ -92,14 +93,14 @@ const loadBodySchema = Joi.object({ /* istanbul ignore next */ export const invokeLoadWalletAsync = async (xpubkey: string, maxGap: number): Promise => { const client = new LambdaClient({ - endpoint: process.env.STAGE === 'dev' + endpoint: config.stage === 'dev' ? 'http://localhost:3002' - : `https://lambda.${process.env.AWS_REGION}.amazonaws.com`, - region: process.env.AWS_REGION, + : `https://lambda.${config.awsRegion}.amazonaws.com`, + region: config.awsRegion, }); const command = new InvokeCommand({ // FunctionName is composed of: service name - stage - function name - FunctionName: `${process.env.SERVICE_NAME}-${process.env.STAGE}-loadWalletAsync`, + FunctionName: `${config.serviceName}-${config.stage}-loadWalletAsync`, InvocationType: 'Event', Payload: JSON.stringify({ xpubkey, maxGap }), }); @@ -276,7 +277,7 @@ export const load: APIGatewayProxyHandler = middy(async (event) => { const xpubkeyStr = value.xpubkey; const authXpubkeyStr = value.authXpubkey; - const maxGap = parseInt(process.env.MAX_ADDRESS_GAP, 10); + const maxGap = config.maxAddressGap; const timestamp = value.timestamp; const xpubkeySignature = value.xpubkeySignature; @@ -442,6 +443,7 @@ export const loadWalletFailed: Handler = async (event) => { ); } } catch (e) { + logger.error('Error during loadWalletFailed', e); await addAlert( 'Failed to handle loadWalletFailed event', `Failed to process the loadWalletFailed event. This indicates that wallets failed to load and we weren't able to recover, please check the logs as soon as possible.`, diff --git a/packages/wallet-service/src/commons.ts b/packages/wallet-service/src/commons.ts index b8ab2c7c..341de854 100644 --- a/packages/wallet-service/src/commons.ts +++ b/packages/wallet-service/src/commons.ts @@ -19,8 +19,6 @@ import { unlockUtxos as dbUnlockUtxos, updateAddressLockedBalance, updateWalletLockedBalance, - getVersionData, - updateVersionData, getBlockByHeight, getTxsAfterHeight, markTxsAsVoided, @@ -47,7 +45,6 @@ import { Wallet, Block, WalletTokenBalance, - FullNodeVersionData, AddressBalance, AddressTotalBalance, WalletProxyHandler, @@ -64,16 +61,13 @@ import { } from '@wallet-service/common/src/types'; import { addAlert } from '@wallet-service/common/src/utils/alerting.utils'; -import { - getUnixTimestamp, - isTxVoided, -} from '@src/utils'; +import { isTxVoided } from '@src/utils'; import hathorLib from '@hathor/wallet-lib'; import { stringMapIterator, WalletBalanceMapConverter } from '@src/db/utils'; +import config from '@src/config'; -const VERSION_CHECK_MAX_DIFF = 60 * 60 * 1000; // 1 hour -const WARN_MAX_REORG_SIZE = parseInt(process.env.WARN_MAX_REORG_SIZE || '100', 10); +const WARN_MAX_REORG_SIZE = config.warnMaxReorgSize; /** * Update the unlocked/locked balances for addresses and wallets connected to the given UTXOs. @@ -294,49 +288,6 @@ export const getWalletBalances = async ( return balances; }; -/** - * Updates the wallet-lib constants if needed. - * - * @returns {Promise} A promise that resolves when the wallet-lib constants have been set. - */ -export const maybeRefreshWalletConstants = async (mysql: ServerlessMysql): Promise => { - const lastVersionData: FullNodeVersionData = await getVersionData(mysql); - const now = getUnixTimestamp(); - - if (!lastVersionData || now - lastVersionData.timestamp > VERSION_CHECK_MAX_DIFF) { - // Query and update versions - // XXX: DEC-0001 - const apiResponse = await hathorLib.versionApi.asyncGetVersion(); - // const apiResponse = await hathorLib.version.checkApiVersion(); - const versionData: FullNodeVersionData = { - timestamp: now, - version: apiResponse.version, - network: apiResponse.network, - minWeight: apiResponse.min_weight, - minTxWeight: apiResponse.min_tx_weight, - minTxWeightCoefficient: apiResponse.min_tx_weight_coefficient, - minTxWeightK: apiResponse.min_tx_weight_k, - tokenDepositPercentage: apiResponse.token_deposit_percentage, - rewardSpendMinBlocks: apiResponse.reward_spend_min_blocks, - maxNumberInputs: apiResponse.max_number_inputs, - maxNumberOutputs: apiResponse.max_number_outputs, - }; - - await updateVersionData(mysql, versionData); - } else { - // XXX: DEC-0001 - // hathorLib.transaction.updateTransactionWeightConstants( - // lastVersionData.minTxWeight, - // lastVersionData.minTxWeightCoefficient, - // lastVersionData.minTxWeightK, - // ); - // hathorLib.tokens.updateDepositPercentage(lastVersionData.tokenDepositPercentage); - // hathorLib.transaction.updateMaxInputsConstant(lastVersionData.maxNumberInputs); - // hathorLib.transaction.updateMaxOutputsConstant(lastVersionData.maxNumberOutputs); - // hathorLib.wallet.updateRewardLockConstant(lastVersionData.rewardSpendMinBlocks); - } -}; - /** * Searches our blocks database for the last block that is not voided. * diff --git a/packages/wallet-service/src/config.ts b/packages/wallet-service/src/config.ts new file mode 100644 index 00000000..48b2b4b7 --- /dev/null +++ b/packages/wallet-service/src/config.ts @@ -0,0 +1,76 @@ +/** + * 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 { EnvironmentConfig } from '@src/types'; +import { EnvironmentConfigSchema } from '@src/schemas'; +import { Severity, addAlert } from '@wallet-service/common'; + +export function loadEnvConfig(): EnvironmentConfig { + const config: EnvironmentConfig = { + defaultServer: process.env.DEFAULT_SERVER ?? 'https://node1.mainnet.hathor.network/v1a/', + stage: process.env.STAGE, + network: process.env.NETWORK, + serviceName: process.env.SERVICE_NAME, + maxAddressGap: Number.parseInt(process.env.MAX_ADDRESS_GAP, 10), + voidedTxOffset: Number.parseInt(process.env.VOIDED_TX_OFFSET, 10), + blockRewardLock: Number.parseInt(process.env.BLOCK_REWARD_LOCK, 10), + confirmFirstAddress: process.env.CONFIRM_FIRST_ADDRESS === 'true', + wsDomain: process.env.WS_DOMAIN, + dbEndpoint: process.env.DB_ENDPOINT, + dbName: process.env.DB_NAME, + dbUser: process.env.DB_USER, + dbPort: parseInt(process.env.DB_PORT, 10), + dbPass: process.env.DB_PASS, + redisUrl: process.env.REDIS_URL, + redisPassword: process.env.REDIS_PASSWORD, + authSecret: process.env.AUTH_SECRET, + walletServiceLambdaEndpoint: process.env.WALLET_SERVICE_LAMBDA_ENDPOINT, + pushNotificationEnabled: process.env.PUSH_NOTIFICATION_ENABLED === 'true', + pushAllowedProviders: process.env.PUSH_ALLOWED_PROVIDERS, + isOffline: process.env.IS_OFFLINE === 'true', + txHistoryMaxCount: parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10), + healthCheckMaximumHeightDifference: Number(process.env.HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE ?? 5), + + awsRegion: process.env.AWS_REGION, + + firebaseProjectId: process.env.FIREBASE_PROJECT_ID, + firebasePrivateKeyId: process.env.FIREBASE_PRIVATE_KEY_ID, + firebaseClientEmail: process.env.FIREBASE_CLIENT_EMAIL, + firebaseClientId: process.env.FIREBASE_CLIENT_ID, + firebaseAuthUri: process.env.FIREBASE_AUTH_URI, + firebaseTokenUri: process.env.FIREBASE_TOKEN_URI, + firebaseAuthProviderX509CertUrl: process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL, + firebaseClientX509CertUrl: process.env.FIREBASE_CLIENT_X509_CERT_URL, + firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY, + + maxLoadWalletRetries: parseInt(process.env.MAX_LOAD_WALLET_RETRIES || '5', 10), + logLevel: process.env.LOG_LEVEL || 'info', + createNftMaxRetries: parseInt(process.env.CREATE_NFT_MAX_RETRIES || '3', 10), + warnMaxReorgSize: parseInt(process.env.WARN_MAX_REORG_SIZE || '100', 10), + }; + + if (process.env.NODE_ENV === 'test') { + return config; + } + + const { value, error } = EnvironmentConfigSchema.validate(config); + if (error) { + addAlert( + 'Environment config ', + error.message, + Severity.CRITICAL, + null, + // @ts-ignore: cannot import logger on this file, creates an import cycle + { error: console.error }, + ); + throw error; + } + + return value; +}; + +export default loadEnvConfig(); diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index da913f5e..5ba752ca 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -29,7 +29,6 @@ import { Wallet, WalletStatus, WalletTokenBalance, - FullNodeVersionData, Block, Tx, AddressBalance, @@ -39,6 +38,7 @@ import { PushDevice, TxByIdToken, PushDeviceSettings, + FullNodeApiVersionResponse, } from '@src/types'; import { getUnixTimestamp, @@ -57,6 +57,7 @@ import { addAlert } from '@wallet-service/common/src/utils/alerting.utils'; import { TxInput, Severity } from '@wallet-service/common/src/types'; import { Logger } from 'winston'; import createDefaultLogger from '@src/logger'; +import { FullnodeVersionSchema } from '@src/schemas'; const logger: Logger = createDefaultLogger(); @@ -1584,22 +1585,14 @@ export const getWalletUnlockedUtxos = async ( * Update latest version_data on the database * * @param mysql - Database connection + * @param timestamp - Unix timestamp to mark when the data was fetched * @param data - Latest version data to store */ -export const updateVersionData = async (mysql: ServerlessMysql, data: FullNodeVersionData): Promise => { +export const updateVersionData = async (mysql: ServerlessMysql, timestamp: number, data: FullNodeApiVersionResponse): Promise => { const entry = { id: 1, - timestamp: data.timestamp, - version: data.version, - network: data.network, - min_weight: data.minWeight, - min_tx_weight: data.minTxWeight, - min_tx_weight_coefficient: data.minTxWeightCoefficient, - min_tx_weight_k: data.minTxWeightK, - token_deposit_percentage: data.tokenDepositPercentage, - reward_spend_min_blocks: data.rewardSpendMinBlocks, - max_number_inputs: data.maxNumberInputs, - max_number_outputs: data.maxNumberOutputs, + timestamp, + data: JSON.stringify(data), }; await mysql.query( @@ -1614,30 +1607,28 @@ export const updateVersionData = async (mysql: ServerlessMysql, data: FullNodeVe * @param mysql - Database connection * @returns */ -export const getVersionData = async (mysql: ServerlessMysql): Promise => { +export const getVersionData = async (mysql: ServerlessMysql): Promise<{ timestamp: number, data: FullNodeApiVersionResponse } | null> => { const results: DbSelectResult = await mysql.query('SELECT * FROM `version_data` WHERE id = 1 LIMIT 1;'); if (results.length > 0) { const data = results[0]; - const entry: FullNodeVersionData = { + const entry: FullNodeApiVersionResponse = JSON.parse(data.data as string); + const { error } = FullnodeVersionSchema.validate(entry); + if (error) { + throw error; + } + + return { timestamp: data.timestamp as number, - version: data.version as string, - network: data.network as string, - minWeight: data.min_weight as number, - minTxWeight: data.min_tx_weight as number, - minTxWeightCoefficient: data.min_tx_weight_coefficient as number, - minTxWeightK: data.min_tx_weight_k as number, - tokenDepositPercentage: data.token_deposit_percentage as number, - rewardSpendMinBlocks: data.reward_spend_min_blocks as number, - maxNumberInputs: data.max_number_inputs as number, - maxNumberOutputs: data.max_number_outputs as number, + data: entry as FullNodeApiVersionResponse, }; - - return entry; } - return null; + return { + timestamp: 0, + data: null, + }; }; /** @@ -3093,7 +3084,7 @@ export const getPushDeviceSettingsList = async ( * @param mysql - Database connection * @returns - total of stale device from now */ -export const countStalePushDevices = async (mysql): Promise => { +export const countStalePushDevices = async (mysql: ServerlessMysql): Promise => { const [{ count }] = await mysql.query( ` SELECT COUNT(device_id) as count @@ -3108,7 +3099,7 @@ export const countStalePushDevices = async (mysql): Promise => { * * @param mysql - Database connection */ -export const deleteStalePushDevices = async (mysql) => { +export const deleteStalePushDevices = async (mysql: ServerlessMysql) => { await mysql.query( ` DELETE diff --git a/packages/wallet-service/src/fullnode.ts b/packages/wallet-service/src/fullnode.ts index 318e610c..3e024ed2 100644 --- a/packages/wallet-service/src/fullnode.ts +++ b/packages/wallet-service/src/fullnode.ts @@ -6,8 +6,12 @@ */ import axios from 'axios'; +import Joi from 'joi'; +import config from '@src/config'; +import { FullNodeApiVersionResponse } from '@src/types'; +import { FullnodeVersionSchema } from '@src/schemas'; -export const BASE_URL = process.env.DEFAULT_SERVER; +export const BASE_URL = config.defaultServer; export const TIMEOUT = 10000; /** @@ -15,13 +19,26 @@ export const TIMEOUT = 10000; * * @param baseURL - The base URL for the full-node. Defaults to `env.DEFAULT_SERVER` */ -export const create = (baseURL = BASE_URL): any => { +export const create = (baseURL = BASE_URL) => { const api = axios.create({ baseURL, headers: {}, timeout: TIMEOUT, }); + const version = async (): Promise => { + const response = await api.get('version', { + data: null, + headers: { 'content-type': 'application/json' }, + }); + const { value, error } = FullnodeVersionSchema.validate(response.data); + if (error) { + throw new Error(error.message); + } + + return value as FullNodeApiVersionResponse; + }; + const downloadTx = async (txId: string) => { const response = await api.get(`transaction?id=${txId}`, { data: null, @@ -74,6 +91,7 @@ export const create = (baseURL = BASE_URL): any => { return { api, // exported so we can mock it on the tests + version, downloadTx, getConfirmationData, queryGraphvizNeighbours, diff --git a/packages/wallet-service/src/logger.ts b/packages/wallet-service/src/logger.ts index 57d01871..0e582362 100644 --- a/packages/wallet-service/src/logger.ts +++ b/packages/wallet-service/src/logger.ts @@ -1,7 +1,8 @@ import { createLogger, format, transports, Logger } from 'winston'; +import config from '@src/config'; const createDefaultLogger = (): Logger => createLogger({ - level: process.env.LOG_LEVEL || 'info', + level: config.logLevel, format: format.json(), transports: [ new transports.Console(), diff --git a/packages/wallet-service/src/mempool.ts b/packages/wallet-service/src/mempool.ts index 4ed2e7d3..4b9f2a53 100644 --- a/packages/wallet-service/src/mempool.ts +++ b/packages/wallet-service/src/mempool.ts @@ -22,6 +22,7 @@ import { import createDefaultLogger from '@src/logger'; import { Severity } from '@wallet-service/common/src/types'; import { addAlert } from '@wallet-service/common/src/utils/alerting.utils'; +import config from '@src/config'; const mysql = getDbConnection(); @@ -36,7 +37,7 @@ const mysql = getDbConnection(); export const onHandleOldVoidedTxs = async (): Promise => { const logger = createDefaultLogger(); - const VOIDED_TX_OFFSET: number = parseInt(process.env.VOIDED_TX_OFFSET, 10) * 60; // env is in minutes + const VOIDED_TX_OFFSET: number = config.voidedTxOffset * 60; // env is in minutes const bestBlock: Block = await getLatestBlockByHeight(mysql); const bestBlockTimestamp = bestBlock.timestamp; @@ -44,7 +45,7 @@ export const onHandleOldVoidedTxs = async (): Promise => { // Fetch voided transactions that are older than 20m const voidedTransactions: Tx[] = await getMempoolTransactionsBeforeDate(mysql, date); - logger.debug(`Found ${voidedTransactions.length} voided transactions older than ${process.env.VOIDED_TX_OFFSET}m from the best block`, { + logger.debug(`Found ${voidedTransactions.length} voided transactions older than ${config.voidedTxOffset}m from the best block`, { voidedTransactions, }); diff --git a/packages/wallet-service/src/metrics.ts b/packages/wallet-service/src/metrics.ts index bdf45e72..22f8849c 100644 --- a/packages/wallet-service/src/metrics.ts +++ b/packages/wallet-service/src/metrics.ts @@ -11,13 +11,14 @@ import 'source-map-support/register'; import { getLatestHeight } from '@src/db'; import { closeDbConnection, getDbConnection } from '@src/utils'; +import config from '@src/config'; const mysql = getDbConnection(); // Default labels const defaultLabels = { - network: process.env.NETWORK, - environment: process.env.STAGE, + network: config.network, + environment: config.stage, }; promClient.register.setDefaultLabels(defaultLabels); diff --git a/packages/wallet-service/src/nodeConfig.ts b/packages/wallet-service/src/nodeConfig.ts new file mode 100644 index 00000000..51543eb1 --- /dev/null +++ b/packages/wallet-service/src/nodeConfig.ts @@ -0,0 +1,53 @@ +/** + * 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 { getUnixTimestamp } from '@src/utils'; +import { ServerlessMysql } from 'serverless-mysql'; +import { getVersionData, updateVersionData } from '@src/db'; +import { FullNodeVersionData, FullNodeApiVersionResponse } from '@src/types'; +import fullnode from '@src/fullnode'; + +const VERSION_CHECK_MAX_DIFF = 60 * 60; // 1 hour + +export async function getRawFullnodeData(mysql: ServerlessMysql): Promise { + const { + timestamp, + data: lastVersionData, + } = await getVersionData(mysql); + const now = getUnixTimestamp(); + + if (!lastVersionData || now - timestamp > VERSION_CHECK_MAX_DIFF) { + const versionData = await fullnode.version(); + await updateVersionData(mysql, timestamp, versionData); + return versionData; + } + + return lastVersionData; +} + +export function convertApiVersionData(data: FullNodeApiVersionResponse): FullNodeVersionData { + return { + version: data.version, + network: data.network, + minWeight: data.min_weight, + minTxWeight: data.min_tx_weight, + minTxWeightCoefficient: data.min_tx_weight_coefficient, + minTxWeightK: data.min_tx_weight_k, + tokenDepositPercentage: data.token_deposit_percentage, + rewardSpendMinBlocks: data.reward_spend_min_blocks, + maxNumberInputs: data.max_number_inputs, + maxNumberOutputs: data.max_number_outputs, + decimalPlaces: data.decimal_places, + nativeTokenName: data.native_token.name, + nativeTokenSymbol: data.native_token.symbol, + }; +} + +export async function getFullnodeData(mysql: ServerlessMysql): Promise { + const data = await getRawFullnodeData(mysql); + return convertApiVersionData(data); +} diff --git a/packages/wallet-service/src/redis.ts b/packages/wallet-service/src/redis.ts index 02328a68..20456b83 100644 --- a/packages/wallet-service/src/redis.ts +++ b/packages/wallet-service/src/redis.ts @@ -5,10 +5,11 @@ import { import redis from 'redis'; import { promisify } from 'util'; +import config from '@src/config'; const redisConfig: RedisConfig = { - url: process.env.REDIS_URL, - password: process.env.REDIS_PASSWORD, + url: config.redisUrl, + password: config.redisPassword, }; export const svcPrefix = 'walletsvc'; diff --git a/packages/wallet-service/src/schemas.ts b/packages/wallet-service/src/schemas.ts new file mode 100644 index 00000000..be878377 --- /dev/null +++ b/packages/wallet-service/src/schemas.ts @@ -0,0 +1,72 @@ +/** + * 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 Joi from 'joi'; +import { FullNodeApiVersionResponse, EnvironmentConfig } from '@src/types'; + +export const FullnodeVersionSchema = Joi.object({ + version: Joi.string().min(1).required(), + network: Joi.string().min(1).required(), + min_weight: Joi.number().integer().positive().required(), + min_tx_weight: Joi.number().integer().positive().required(), + min_tx_weight_coefficient: Joi.number().positive().required(), + min_tx_weight_k: Joi.number().integer().positive().required(), + token_deposit_percentage: Joi.number().positive().required(), + reward_spend_min_blocks: Joi.number().integer().positive().required(), + max_number_inputs: Joi.number().integer().positive().required(), + max_number_outputs: Joi.number().integer().positive().required(), + decimal_places: Joi.number().integer().positive().required(), + genesis_block_hash: Joi.string().min(1).required(), + genesis_tx1_hash: Joi.string().hex().length(64).required(), + genesis_tx2_hash: Joi.string().hex().length(64).required(), + native_token: Joi.object({ + name: Joi.string().min(1).max(30).required(), + symbol: Joi.string().min(1).max(5).required(), + }), +}).unknown(true); + +export const EnvironmentConfigSchema = Joi.object({ + defaultServer: Joi.string().required(), + stage: Joi.string().required(), + network: Joi.string().required(), + serviceName: Joi.string().required(), + maxAddressGap: Joi.number().required(), + voidedTxOffset: Joi.number().required(), + blockRewardLock: Joi.number().required(), + confirmFirstAddress: Joi.boolean().required(), + wsDomain: Joi.string().required(), + dbEndpoint: Joi.string().required(), + dbName: Joi.string().required(), + dbUser: Joi.string().required(), + dbPass: Joi.string().required(), + dbPort: Joi.number().required(), + redisUrl: Joi.string().required(), + redisPassword: Joi.string(), + authSecret: Joi.string().required(), + walletServiceLambdaEndpoint: Joi.string().required(), + pushNotificationEnabled: Joi.boolean().required(), + pushAllowedProviders: Joi.string().required(), + isOffline: Joi.boolean().required(), + txHistoryMaxCount: Joi.number().required(), + healthCheckMaximumHeightDifference: Joi.number().required(), + awsRegion: Joi.string().required(), + + firebaseProjectId: Joi.string().required(), + firebasePrivateKeyId: Joi.string().required(), + firebaseClientEmail: Joi.string().required(), + firebaseClientId: Joi.string().required(), + firebaseAuthUri: Joi.string().required(), + firebaseTokenUri: Joi.string().required(), + firebaseAuthProviderX509CertUrl: Joi.string().required(), + firebaseClientX509CertUrl: Joi.string().required(), + firebasePrivateKey: Joi.string().allow(null).required(), + + maxLoadWalletRetries: Joi.number().required(), + logLevel: Joi.string().required(), + createNftMaxRetries: Joi.number().required(), + warnMaxReorgSize: Joi.number().required(), +}); diff --git a/packages/wallet-service/src/txProcessor.ts b/packages/wallet-service/src/txProcessor.ts index a3748243..57c37e59 100644 --- a/packages/wallet-service/src/txProcessor.ts +++ b/packages/wallet-service/src/txProcessor.ts @@ -9,8 +9,9 @@ import { Handler } from 'aws-lambda'; import 'source-map-support/register'; import createDefaultLogger from '@src/logger'; import { NftUtils } from '@wallet-service/common/src/utils/nft.utils'; +import config from '@src/config'; -export const CREATE_NFT_MAX_RETRIES: number = parseInt(process.env.CREATE_NFT_MAX_RETRIES || '3', 10); +export const CREATE_NFT_MAX_RETRIES: number = config.createNftMaxRetries; /** * This intermediary handler is responsible for making the final validations and calling diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index 1f911035..aa0fe50e 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -39,8 +39,53 @@ export enum TxProposalStatus { CANCELLED = 'cancelled', } +/** + * wallet-service environment config. + */ +export interface EnvironmentConfig { + defaultServer: string; + stage: string; + network: string; + serviceName: string; + maxAddressGap: number; + voidedTxOffset: number; + blockRewardLock: number; // * + confirmFirstAddress: boolean; + wsDomain: string; + dbEndpoint: string; + dbName: string; + dbUser: string; + dbPass: string; + dbPort: number; + redisUrl: string; + redisPassword: string; + authSecret: string; + walletServiceLambdaEndpoint: string; + pushNotificationEnabled: boolean; + pushAllowedProviders: string; + isOffline: boolean; + txHistoryMaxCount: number; + healthCheckMaximumHeightDifference: number; + awsRegion: string; + firebaseProjectId: string; + firebasePrivateKeyId: string; + firebaseClientEmail: string; + firebaseClientId: string; + firebaseAuthUri: string; + firebaseTokenUri: string; + firebaseAuthProviderX509CertUrl: string; + firebaseClientX509CertUrl: string; + firebasePrivateKey: string|null; + maxLoadWalletRetries: number; + logLevel: string; + createNftMaxRetries: number; + warnMaxReorgSize: number; +}; + +/** + * Fullnode converted version data. + */ export interface FullNodeVersionData { - timestamp: number; version: string; network: string; minWeight: number; @@ -51,6 +96,30 @@ export interface FullNodeVersionData { rewardSpendMinBlocks: number; maxNumberInputs: number; maxNumberOutputs: number; + decimalPlaces: number; + nativeTokenName: string; + nativeTokenSymbol: string; +} + +/** + * Fullnode API response. + */ +export interface FullNodeApiVersionResponse { + version: string; + network: string; + min_weight: number; + min_tx_weight: number; + min_tx_weight_coefficient: number; // float + min_tx_weight_k: number; + token_deposit_percentage: number; // float + reward_spend_min_blocks: number; + max_number_inputs: number; + max_number_outputs: number; + decimal_places: number; + genesis_block_hash: string, + genesis_tx1_hash: string, + genesis_tx2_hash: string, + native_token: { name: string, symbol: string}; } export interface TxProposal { diff --git a/packages/wallet-service/src/utils.ts b/packages/wallet-service/src/utils.ts index 6a740a88..06809ab1 100644 --- a/packages/wallet-service/src/utils.ts +++ b/packages/wallet-service/src/utils.ts @@ -15,51 +15,11 @@ import * as bitcoin from 'bitcoinjs-lib'; import * as bitcoinMessage from 'bitcoinjs-message'; import * as ecc from 'tiny-secp256k1'; import BIP32Factory from 'bip32'; +import config from '@src/config'; const bip32 = BIP32Factory(ecc); -// XXX - Check effects of this storage -/* TODO: We should remove this as soon as the wallet-lib is refactored -* (https://github.com/HathorNetwork/hathor-wallet-lib/issues/122) -*/ -export class CustomStorage { - store: unknown; - - constructor() { - this.preStart(); - } - - getItem(key: string): string { - return this.store[key]; - } - - setItem(key: string, value: string): string { - this.store[key] = value; - - return value; - } - - removeItem(key: string): string { - delete this.store[key]; - - return key; - } - - clear(): void { - this.store = {}; - } - - preStart(): void { - // XXX: DEC-0002 - // DEFAULT_SERVER - 'https://node1.mainnet.hathor.network/v1a/' - this.store = { - 'wallet:server': process.env.DEFAULT_SERVER || 'https://node1.mainnet.hathor.network/v1a/', - 'wallet:defaultServer': process.env.DEFAULT_SERVER || 'https://node1.mainnet.hathor.network/v1a/', - }; - } -} - -hathorLib.network.setNetwork(process.env.NETWORK); +hathorLib.network.setNetwork(config.network); const libNetwork = hathorLib.network.getNetwork(); const hathorNetwork = { @@ -119,19 +79,19 @@ export const getUnixTimestamp = (): number => ( export const getDbConnection = (): ServerlessMysql => ( serverlessMysql({ config: { - host: process.env.DB_ENDPOINT, - database: process.env.DB_NAME, - user: process.env.DB_USER, - port: parseInt(process.env.DB_PORT, 10), + host: config.dbEndpoint, + database: config.dbName, + user: config.dbUser, + port: config.dbPort, // TODO if not on local env, get IAM token // https://aws.amazon.com/blogs/database/iam-role-based-authentication-to-amazon-aurora-from-serverless-applications/ - password: process.env.DB_PASS, + password: config.dbPass, }, }) ); export const closeDbConnection = async (mysql: ServerlessMysql): Promise => { - if (process.env.STAGE === 'local') { + if (config.stage === 'local') { // mysql.end() leaves the function hanging in the local environment. Some issues: // https://github.com/jeremydaly/serverless-mysql/issues/61 // https://github.com/jeremydaly/serverless-mysql/issues/79 diff --git a/packages/wallet-service/src/utils/pushnotification.utils.ts b/packages/wallet-service/src/utils/pushnotification.utils.ts index 9aa5362c..8e5ef5af 100644 --- a/packages/wallet-service/src/utils/pushnotification.utils.ts +++ b/packages/wallet-service/src/utils/pushnotification.utils.ts @@ -11,6 +11,7 @@ import { Severity } from '@wallet-service/common/src/types'; import fcmAdmin, { credential, messaging, ServiceAccount } from 'firebase-admin'; import { MulticastMessage } from 'firebase-admin/messaging'; import createDefaultLogger from '@src/logger'; +import config from '@src/config'; import { assertEnvVariablesExistence } from '@wallet-service/common/src/utils/index.utils'; import { addAlert } from '@wallet-service/common/src/utils/alerting.utils'; @@ -44,7 +45,7 @@ try { } export function buildFunctionName(functionName: string): string { - return `hathor-wallet-service-${process.env.STAGE}-${functionName}`; + return `hathor-wallet-service-${config.stage}-${functionName}`; } export enum FunctionName { @@ -52,19 +53,19 @@ export enum FunctionName { ON_TX_PUSH_NOTIFICATION_REQUESTED = 'txPushRequested', } -const STAGE = process.env.STAGE; -const AWS_REGION = process.env.AWS_REGION; -const WALLET_SERVICE_LAMBDA_ENDPOINT = process.env.WALLET_SERVICE_LAMBDA_ENDPOINT; +const STAGE = config.stage; +const AWS_REGION = config.awsRegion; +const WALLET_SERVICE_LAMBDA_ENDPOINT = config.walletServiceLambdaEndpoint; const SEND_NOTIFICATION_FUNCTION_NAME = buildFunctionName(FunctionName.SEND_NOTIFICATION_TO_DEVICE); const ON_TX_PUSH_NOTIFICATION_REQUESTED_FUNCTION_NAME = buildFunctionName(FunctionName.ON_TX_PUSH_NOTIFICATION_REQUESTED); -const FIREBASE_PROJECT_ID = process.env.FIREBASE_PROJECT_ID; -const FIREBASE_PRIVATE_KEY_ID = process.env.FIREBASE_PRIVATE_KEY_ID; -const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL; -const FIREBASE_CLIENT_ID = process.env.FIREBASE_CLIENT_ID; -const FIREBASE_AUTH_URI = process.env.FIREBASE_AUTH_URI; -const FIREBASE_TOKEN_URI = process.env.FIREBASE_TOKEN_URI; -const FIREBASE_AUTH_PROVIDER_X509_CERT_URL = process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL; -const FIREBASE_CLIENT_X509_CERT_URL = process.env.FIREBASE_CLIENT_X509_CERT_URL; +const FIREBASE_PROJECT_ID = config.firebaseProjectId; +const FIREBASE_PRIVATE_KEY_ID = config.firebasePrivateKeyId; +const FIREBASE_CLIENT_EMAIL = config.firebaseClientEmail; +const FIREBASE_CLIENT_ID = config.firebaseClientId; +const FIREBASE_AUTH_URI = config.firebaseAuthUri; +const FIREBASE_TOKEN_URI = config.firebaseTokenUri; +const FIREBASE_AUTH_PROVIDER_X509_CERT_URL = config.firebaseAuthProviderX509CertUrl; +const FIREBASE_CLIENT_X509_CERT_URL = config.firebaseClientX509CertUrl; const FIREBASE_PRIVATE_KEY = (() => { try { /** @@ -73,7 +74,7 @@ const FIREBASE_PRIVATE_KEY = (() => { * the escaped line break with an unescaped line break. * https://github.com/gladly-team/next-firebase-auth/discussions/95#discussioncomment-2891225 */ - const privateKey = process.env.FIREBASE_PRIVATE_KEY; + const privateKey = config.firebasePrivateKey; return privateKey ? privateKey.replace(/\\n/gm, '\n') : null; @@ -84,7 +85,7 @@ const FIREBASE_PRIVATE_KEY = (() => { })(); /** Local feature toggle that disable the push notification by default */ -const PUSH_NOTIFICATION_ENABLED = process.env.PUSH_NOTIFICATION_ENABLED; +const PUSH_NOTIFICATION_ENABLED = config.pushNotificationEnabled; /** * Controls which providers are allowed to send notification when it is enabled * @example @@ -101,7 +102,7 @@ const PUSH_NOTIFICATION_ENABLED = process.env.PUSH_NOTIFICATION_ENABLED; * ``` * */ const PUSH_ALLOWED_PROVIDERS = (() => { - const providers = process.env.PUSH_ALLOWED_PROVIDERS; + const providers = config.pushAllowedProviders; if (!providers) { // If no providers are set, we allow android by default, but alert the environment variable is empty logger.error('[ALERT] env.PUSH_ALLOWED_PROVIDERS is empty.'); @@ -112,7 +113,8 @@ const PUSH_ALLOWED_PROVIDERS = (() => { export const isPushProviderAllowed = (provider: string): boolean => PUSH_ALLOWED_PROVIDERS.includes(provider); -export const isPushNotificationEnabled = (): boolean => PUSH_NOTIFICATION_ENABLED === 'true'; +// XXX: PUSH_NOTIFICATION_ENABLED already is a boolean, this became an identity function +export const isPushNotificationEnabled = (): boolean => PUSH_NOTIFICATION_ENABLED; const serviceAccount = { type: 'service_account', diff --git a/packages/wallet-service/src/ws/utils.ts b/packages/wallet-service/src/ws/utils.ts index f933a186..1c28dc33 100644 --- a/packages/wallet-service/src/ws/utils.ts +++ b/packages/wallet-service/src/ws/utils.ts @@ -11,6 +11,7 @@ import { } from '@aws-sdk/client-apigatewaymanagementapi'; import { Logger } from 'winston'; import createDefaultLogger from '@src/logger'; +import config from '@src/config'; import util from 'util'; import { Severity } from '@wallet-service/common/src/types'; @@ -24,7 +25,7 @@ export const connectionInfoFromEvent = ( ): WsConnectionInfo => { const logger: Logger = createDefaultLogger(); const connID = event.requestContext.connectionId; - if (process.env.IS_OFFLINE === 'true') { + if (config.isOffline) { // This will enter when running the service on serverless offline mode return { id: connID, @@ -32,7 +33,7 @@ export const connectionInfoFromEvent = ( }; } - const domain = process.env.WS_DOMAIN; + const domain = config.wsDomain; if (!domain) { addAlert( diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 7d306c90..7ea2c8bd 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -1,5 +1,6 @@ import { APIGatewayProxyHandler, APIGatewayProxyResult } from 'aws-lambda'; +import { mockedAddAlert } from '@tests/utils/alerting.utils.mock'; import { get as addressesGet, checkMine } from '@src/api/addresses'; import { get as newAddressesGet } from '@src/api/newAddresses'; import { get as balancesGet } from '@src/api/balances'; @@ -19,17 +20,19 @@ import { get as walletGet, load as walletLoad, loadWallet, + loadWalletFailed, changeAuthXpub, } from '@src/api/wallet'; import { updateVersionData, + createWallet, } from '@src/db'; import * as Wallet from '@src/api/wallet'; import * as Db from '@src/db'; import { ApiError } from '@src/api/errors'; import { closeDbConnection, getDbConnection, getUnixTimestamp, getWalletId } from '@src/utils'; import { STATUS_CODE_TABLE } from '@src/api/utils'; -import { WalletStatus, FullNodeVersionData } from '@src/types'; +import { WalletStatus, FullNodeApiVersionResponse } from '@src/types'; import { walletUtils, addressUtils, constants, network, HathorWalletServiceWallet } from '@hathor/wallet-lib'; import bitcore from 'bitcore-lib'; import { @@ -52,10 +55,12 @@ import { makeGatewayEventWithAuthorizer, getAuthData, getXPrivKeyFromSeed, + makeLoadWalletFailedSNSEvent, } from '@tests/utils'; import fullnode from '@src/fullnode'; import { getHealthcheck } from '@src/api/healthcheck'; -import { ping } from "@src/redis"; +import { Severity } from '@wallet-service/common'; +import { ErrorMessages } from '@hathor/wallet-lib/lib/errorMessages'; // Monkey patch bitcore-lib @@ -1154,6 +1159,53 @@ test('changeAuthXpub should fail if signatures do not match', async () => { expect(returnBody.details[0].message).toBe('Signatures are not valid'); }); +test('PUT /wallet/auth should fail if we cannot confirm the firstAddress', async () => { + expect.hasAssertions(); + + // get the first address + const xpubChangeDerivation = walletUtils.xpubDeriveChild(XPUBKEY, 0); + + const firstAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 0, process.env.NETWORK); + const firstAddress = firstAddressData.base58; + // Get address at wrong derivation index + const secondAddressData = addressUtils.deriveAddressFromXPubP2PKH(xpubChangeDerivation, 1, process.env.NETWORK); + const secondAddress = secondAddressData.base58; + + // we need signatures for both the account path and the purpose path: + const now = Math.floor(Date.now() / 1000); + const walletId = getWalletId(XPUBKEY); + const xpriv = getXPrivKeyFromSeed(TEST_SEED, { + passphrase: '', + networkName: process.env.NETWORK, + }); + + const wallet = await createWallet(mysql, walletId, XPUBKEY, AUTH_XPUBKEY, 20); + + // account path + const accountDerivationIndex = '0\''; + + const derivedPrivKey = walletUtils.deriveXpriv(xpriv, accountDerivationIndex); + const address = derivedPrivKey.publicKey.toAddress(network.getNetwork()).toString(); + const message = new bitcore.Message(String(now).concat(walletId).concat(address)); + + const event = makeGatewayEvent({}, JSON.stringify({ + xpubkey: XPUBKEY, + xpubkeySignature: 'xpubkey-signature', + authXpubkey: AUTH_XPUBKEY, + authXpubkeySignature: 'auth-xpubkey-signature', + firstAddress: ADDRESSES[1], + timestamp: Math.floor(Date.now() / 1000), + })); + + const result = await changeAuthXpub(event, null, null) as APIGatewayProxyResult; + const returnBody = JSON.parse(result.body as string); + + expect(result.statusCode).toBe(400); + expect(returnBody.success).toBe(false); + expect(returnBody.error).toBe('invalid-payload'); + expect(returnBody.message).toBe(`Expected first address to be ${secondAddress} but it is ${firstAddress}`); +}, 30000); + test('PUT /wallet/auth should change the auth_xpub only after validating both the xpub and the auth_xpubkey', async () => { expect.hasAssertions(); @@ -1327,6 +1379,27 @@ test('loadWallet should update wallet status to ERROR if an error occurs', async expect(wallet.status).toStrictEqual(WalletStatus.ERROR); }, 30000); +test('loadWalletFailed should create alert if xpubkey is missing', async () => { + expect.hasAssertions(); + + const event = makeLoadWalletFailedSNSEvent(1, XPUBKEY, 'a-req-01', 'an-error-01'); + event.Records[0].Sns.Message = '{}'; + mockedAddAlert.mockReset(); + + await loadWalletFailed(event, null, null); + + expect(mockedAddAlert).toHaveBeenCalledWith( + 'Wallet failed to load, but no xpubkey received.', + `An event reached loadWalletFailed lambda but the xpubkey was not sent. This indicates that a wallet has failed to load and we weren't able to recover, please check the logs as soon as possible.`, + Severity.MAJOR, + { + RequestID: 'a-req-01', + ErrorMessage: 'an-error-01' + }, + expect.anything(), + ); +}); + test('GET /wallet/tokens', async () => { expect.hasAssertions(); @@ -1593,21 +1666,26 @@ test('DELETE /tx/proposal/{txProposalId}', async () => { test('GET /version', async () => { expect.hasAssertions(); - const mockData: FullNodeVersionData = { - timestamp: 1614875031449, + const mockData: FullNodeApiVersionResponse = { version: '0.38.0', network: 'mainnet', - minWeight: 14, - minTxWeight: 14, - minTxWeightCoefficient: 1.6, - minTxWeightK: 100, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 255, + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + genesis_tx1_hash: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + genesis_tx2_hash: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', + native_token: { name: 'Hathor', symbol: 'HTR'}, }; - await updateVersionData(mysql, mockData); + const ts = getUnixTimestamp() + await updateVersionData(mysql, ts, mockData); const event = makeGatewayEvent({}); const result = await getVersionDataGet(event, null, null) as APIGatewayProxyResult; diff --git a/packages/wallet-service/tests/commons.test.ts b/packages/wallet-service/tests/commons.test.ts index c4fed141..9e551a59 100644 --- a/packages/wallet-service/tests/commons.test.ts +++ b/packages/wallet-service/tests/commons.test.ts @@ -5,18 +5,18 @@ import { markLockedOutputs, unlockUtxos, unlockTimelockedUtxos, - maybeRefreshWalletConstants, searchForLatestValidBlock, getWalletBalancesForTx, } from '@src/commons'; import { - FullNodeVersionData, Authorities, Balance, TokenBalanceMap, DbTxOutput, Block, + FullNodeApiVersionResponse, } from '@src/types'; +import fullnode from '@src/fullnode'; import { TxInput, TxOutput, @@ -50,6 +50,7 @@ import { } from '@src/db'; import * as Utils from '@src/utils'; import hathorLib from '@hathor/wallet-lib'; +import { convertApiVersionData, getFullnodeData } from '@src/nodeConfig'; const mysql = getDbConnection(); const OLD_ENV = process.env; @@ -510,15 +511,52 @@ test('unlockTimelockedUtxos', async () => { await expect(checkWalletBalanceTable(mysql, 1, walletId, token, 5000, 0, null, 3, 0b10, 0)).resolves.toBe(true); }); -// XXX: DEC-0001 -test('maybeRefreshWalletConstants with an uninitialized version_data database should call hathorLib.versionApi.asyncGetVersion()', async () => { +test('getFullnodeData with an uninitialized version_data database should call the version api', async () => { expect.hasAssertions(); - const spy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); + const mockData = { + version: '0.38.0', + network: 'mainnet', + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: '1234567812345678123456781234567812345678123456781234567812345678', + genesis_tx1_hash: 'abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd', + genesis_tx2_hash: 'd00d500fd00d500fd00d500fd00d500fd00d500fd00d500fd00d500fd00d500f', + native_token: { name: 'Hathor', symbol: 'HTR'}, + }; + + const spy = jest.spyOn(fullnode.api, 'get'); + spy.mockImplementation(() => Promise.resolve({ + status: 200, + data: mockData, + })); + + await mysql.query('DELETE FROM`version_data`'); + + const data = await getFullnodeData(mysql); + console.log(JSON.stringify(data)); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith('version', expect.any(Object)); +}); + +test('getFullnodeData with an initialized version_data database should query data from the database', async () => { + expect.hasAssertions(); - const mockGet = jest.fn(() => Promise.resolve({ - data: { - success: true, + const axiosSpy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); + const mockGet = jest.fn(() => Promise.resolve({ data: {} })); + + // @ts-ignore + axiosSpy.mockReturnValue({ get: mockGet }); + + const mockedVersionData: FullNodeApiVersionResponse = { version: '0.38.0', network: 'mainnet', min_weight: 14, @@ -529,69 +567,21 @@ test('maybeRefreshWalletConstants with an uninitialized version_data database sh reward_spend_min_blocks: 300, max_number_inputs: 255, max_number_outputs: 255, - }, - })); + decimal_places: 2, + genesis_block_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx1_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx2_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + native_token: { name: 'Hathor', symbol: 'HTR'}, + }; - spy.mockReturnValue({ - // @ts-ignore - post: () => Promise.resolve({ - data: { - success: true, - }, - }), - // @ts-ignore - get: mockGet, - }); + await updateVersionData(mysql, new Date().getTime(), mockedVersionData); - await maybeRefreshWalletConstants(mysql); + const data = await getFullnodeData(mysql); - expect(mockGet).toHaveBeenCalledTimes(1); + expect(mockGet).toHaveBeenCalledTimes(0); + expect(data).toEqual(convertApiVersionData(mockedVersionData)); }); -// XXX: DEC-0001 -// test('maybeRefreshWalletConstants with an initialized version_data database should query data from the database', async () => { -// expect.hasAssertions(); - -// const axiosSpy = jest.spyOn(hathorLib.axios, 'createRequestInstance'); -// const mockGet = jest.fn(() => Promise.resolve({ data: {} })); - -// axiosSpy.mockReturnValue({ get: mockGet }); - -// const mockedVersionData: FullNodeVersionData = { -// timestamp: new Date().getTime(), -// version: '0.38.0', -// network: 'mainnet', -// minWeight: 14, -// minTxWeight: 14, -// minTxWeightCoefficient: 1.6, -// minTxWeightK: 100, -// tokenDepositPercentage: 0.01, -// rewardSpendMinBlocks: 300, -// maxNumberInputs: 255, -// maxNumberOutputs: 255, -// }; - -// await updateVersionData(mysql, mockedVersionData); - -// await maybeRefreshWalletConstants(mysql); - -// const { -// txMinWeight, -// txWeightCoefficient, -// txMinWeightK, -// } = hathorLib.transaction.getTransactionWeightConstants(); - -// const maxNumberInputs = hathorLib.transaction.getMaxInputsConstant(); -// const maxNumberOutputs = hathorLib.transaction.getMaxOutputsConstant(); - -// expect(mockGet).toHaveBeenCalledTimes(0); -// expect(txMinWeight).toStrictEqual(mockedVersionData.minTxWeight); -// expect(txWeightCoefficient).toStrictEqual(mockedVersionData.minTxWeightCoefficient); -// expect(txMinWeightK).toStrictEqual(mockedVersionData.minTxWeightK); -// expect(maxNumberInputs).toStrictEqual(mockedVersionData.maxNumberInputs); -// expect(maxNumberOutputs).toStrictEqual(mockedVersionData.maxNumberOutputs); -// }); - test('searchForLatestValidBlock should find the first voided block', async () => { expect.hasAssertions(); diff --git a/packages/wallet-service/tests/db.test.ts b/packages/wallet-service/tests/db.test.ts index 7b32e0b7..857e85e3 100644 --- a/packages/wallet-service/tests/db.test.ts +++ b/packages/wallet-service/tests/db.test.ts @@ -98,12 +98,12 @@ import { TokenInfo, TxProposalStatus, WalletStatus, - FullNodeVersionData, Tx, DbTxOutput, PushDevice, PushProvider, Block, + FullNodeApiVersionResponse, } from '@src/types'; import { Severity } from '@wallet-service/common/src/types'; import { isAuthority } from '@wallet-service/common/src/utils/wallet.utils'; @@ -1586,33 +1586,38 @@ test('createTxProposal, updateTxProposal, getTxProposal, countUnsentTxProposals, test('updateVersionData', async () => { expect.hasAssertions(); - const mockData: FullNodeVersionData = { - timestamp: 1614875031449, + const mockData: FullNodeApiVersionResponse = { version: '0.38.0', network: 'mainnet', - minWeight: 14, - minTxWeight: 14, - minTxWeightCoefficient: 1.6, - minTxWeightK: 100, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 255, + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: '0000000000000000000000000000000000000000000000000000000000000000', + genesis_tx1_hash: '1111111111111111111111111111111111111111111111111111111111111111', + genesis_tx2_hash: '2222222222222222222222222222222222222222222222222222222222222222', + native_token: { name: 'Hathor', symbol: 'HTR'}, }; - const mockData2: FullNodeVersionData = { + const mockData2: FullNodeApiVersionResponse = { ...mockData, version: '0.39.1', }; - const mockData3: FullNodeVersionData = { + const mockData3: FullNodeApiVersionResponse = { ...mockData, version: '0.39.2', }; - await updateVersionData(mysql, mockData); - await updateVersionData(mysql, mockData2); - await updateVersionData(mysql, mockData3); + const ts = getUnixTimestamp(); + await updateVersionData(mysql, ts, mockData); + await updateVersionData(mysql, ts, mockData2); + await updateVersionData(mysql, ts, mockData3); await expect( checkVersionDataTable(mysql, mockData3), @@ -1622,25 +1627,30 @@ test('updateVersionData', async () => { test('getVersionData', async () => { expect.hasAssertions(); - const mockData: FullNodeVersionData = { - timestamp: 1614875031449, + const mockData: FullNodeApiVersionResponse = { version: '0.38.0', network: 'mainnet', - minWeight: 14, - minTxWeight: 14, - minTxWeightCoefficient: 1.6, - minTxWeightK: 100, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 255, + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx1_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx2_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + native_token: { name: 'Hathor', symbol: 'HTR'}, }; - await updateVersionData(mysql, mockData); + const ts = getUnixTimestamp(); + await updateVersionData(mysql, ts, mockData); - const versionData: FullNodeVersionData = await getVersionData(mysql); + const { data } = await getVersionData(mysql); - expect(Object.entries(versionData).toString()).toStrictEqual(Object.entries(mockData).toString()); + expect(Object.entries(data).toString()).toStrictEqual(Object.entries(mockData).toString()); }); test('fetchAddressTxHistorySum', async () => { @@ -3246,7 +3256,7 @@ describe('getPushDeviceSettingsList', () => { // register wallets that will not be queried const loadWallet = (eachDevice) => createWallet(mysql, eachDevice.walletId, XPUBKEY, AUTH_XPUBKEY, 5); - await devicesToLoad.forEach(loadWallet); + await Promise.all(devicesToLoad.map(loadWallet)); // register devices related to the loaded wallets const loadDevice = (eachDevice) => registerPushDevice(mysql, { @@ -3256,7 +3266,7 @@ describe('getPushDeviceSettingsList', () => { enablePush: eachDevice.enablePush, enableShowAmounts: eachDevice.enableShowAmounts, }); - await devicesToLoad.forEach(loadDevice); + await Promise.all(devicesToLoad.map(loadDevice)); // get settings querying only devices not loaded on database, resulting on empty list const notRegisteredWalletIdList = devicesToNotLoad.map((each) => each.walletId); @@ -3308,7 +3318,7 @@ describe('getPushDeviceSettingsList', () => { // register wallets to be used by registered devices const loadWallet = (eachDevice) => createWallet(mysql, eachDevice.walletId, XPUBKEY, AUTH_XPUBKEY, 5); - await devicesToLoad.forEach(loadWallet); + await Promise.all(devicesToLoad.map(loadWallet)); // register devices related to the loaded wallets const loadDevice = (eachDevice) => registerPushDevice(mysql, { @@ -3318,7 +3328,7 @@ describe('getPushDeviceSettingsList', () => { enablePush: eachDevice.enablePush, enableShowAmounts: eachDevice.enableShowAmounts, }); - await devicesToLoad.forEach(loadDevice); + await Promise.all(devicesToLoad.map(loadDevice)); // get settings, query be all wallets of deviceCandidates, some are loaded on database, some are not const walletIdList = deviceCandidates.map((each) => each.walletId); @@ -3381,7 +3391,7 @@ describe('getPushDeviceSettingsList', () => { // register wallets, load all the wallets related to devicesToLoad const loadWallet = (eachDevice) => createWallet(mysql, eachDevice.walletId, XPUBKEY, AUTH_XPUBKEY, 5); - await devicesToLoad.forEach(loadWallet); + await Promise.all(devicesToLoad.map(loadWallet)); // register devices, register all the devices const loadDevice = (eachDevice) => registerPushDevice(mysql, { @@ -3391,7 +3401,7 @@ describe('getPushDeviceSettingsList', () => { enablePush: eachDevice.enablePush, enableShowAmounts: eachDevice.enableShowAmounts, }); - await devicesToLoad.forEach(loadDevice); + await Promise.all(devicesToLoad.map(loadDevice)); // get settings, get every device registered const walletIdList = devicesToLoad.map((each) => each.walletId); diff --git a/packages/wallet-service/tests/env.test.ts b/packages/wallet-service/tests/env.test.ts new file mode 100644 index 00000000..076985d7 --- /dev/null +++ b/packages/wallet-service/tests/env.test.ts @@ -0,0 +1,25 @@ +import config, { loadEnvConfig } from '@src/config'; + +test('Configuration should load correctly during tests', () => { + expect.hasAssertions(); + + const loadedConfig = loadEnvConfig(); + expect(loadedConfig).toStrictEqual(config); + + expect(config.confirmFirstAddress).toEqual(true); +}); + +test('loadEnvConfig should get the config from the env', () => { + expect.hasAssertions(); + + const oldNetwork = process.env.NETWORK; + process.env.NETWORK = 'unknown unexisting network'; + + try { + const loadedConfig = loadEnvConfig(); + expect(loadedConfig.network).toEqual('unknown unexisting network'); + } finally { + process.env.NETWORK = oldNetwork; + } + +}); diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index 7d0299ab..a6d86e22 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -36,20 +36,24 @@ beforeEach(async () => { const now = getUnixTimestamp(); const versionData = { - timestamp: now, version: '0.38.4', network: process.env.NETWORK, - minWeight: 8, - minTxWeight: 8, - minTxWeightCoefficient: 0, - minTxWeightK: 0, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 255, + min_weight: 8, + min_tx_weight: 8, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx1_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx2_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + native_token: { name: 'Hathor', symbol: 'HTR'}, }; - await addToVersionDataTable(mysql, versionData); + await addToVersionDataTable(mysql, now, versionData); }); afterAll(async () => { @@ -197,30 +201,34 @@ test('POST /txproposals with utxos that are already used on another txproposal s expect(usedInputsReturnBody.error).toBe(ApiError.INPUTS_ALREADY_USED); }); -// XXX: DEC-0001 -test.skip('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY_OUTPUTS', async () => { +test('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY_OUTPUTS', async () => { expect.hasAssertions(); const now = getUnixTimestamp(); - await updateVersionData(mysql, { - timestamp: now, + await updateVersionData(mysql, now, { version: '0.38.4', network: process.env.NETWORK, - minWeight: 8, - minTxWeight: 8, - minTxWeightCoefficient: 0, - minTxWeightK: 0, - tokenDepositPercentage: 0.01, - rewardSpendMinBlocks: 300, - maxNumberInputs: 255, - maxNumberOutputs: 2, // mocking to force a failure + min_weight: 8, + min_tx_weight: 8, + min_tx_weight_coefficient: 1.8, + min_tx_weight_k: 90, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 2, // mocking to force a failure + decimal_places: 2, + genesis_block_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx1_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + genesis_tx2_hash: 'cafecafecafecafecafecafecafecafecafecafecafecafecafecafecafecafe', + native_token: { name: 'Hathor', symbol: 'HTR'}, }); + jest.resetModules(); await addToWalletTable(mysql, [{ id: 'my-wallet', xpubkey: 'xpubkey', - authXpubkey: 'auth_xpubkey', + authXpubkey: 'auth_xpubkey', status: 'ready', maxGap: 5, createdAt: 10000, diff --git a/packages/wallet-service/tests/utils.test.ts b/packages/wallet-service/tests/utils.test.ts index eaa11379..ce8a3516 100644 --- a/packages/wallet-service/tests/utils.test.ts +++ b/packages/wallet-service/tests/utils.test.ts @@ -1,32 +1,8 @@ -import { CustomStorage, arrayShuffle, sha256d, isTxVoided } from '@src/utils'; +import { arrayShuffle, sha256d, isTxVoided } from '@src/utils'; import hathorLib from '@hathor/wallet-lib'; import * as Fullnode from '@src/fullnode'; import { TEST_SEED, XPUBKEY, AUTH_XPUBKEY, ADDRESSES } from '@tests/utils'; -// XXX: DEC-0002 -test('CustomStorage', () => { - expect.hasAssertions(); - - const store = new CustomStorage(); - // Should be initialized with hathor default server and server - expect(store.getItem('wallet:defaultServer')).toBe('https://node1.mainnet.hathor.network/v1a/'); - expect(store.getItem('wallet:server')).toBe('https://node1.mainnet.hathor.network/v1a/'); - - store.setItem('hathor', 'hathor'); - expect(store.getItem('hathor')).toBe('hathor'); - store.removeItem('hathor'); - - expect(store.getItem('hathor')).toBeUndefined(); - - store.setItem('hathor', 'hathor2'); - store.clear(); - expect(store.getItem('hathor')).toBeUndefined(); - - store.preStart(); - expect(store.getItem('wallet:defaultServer')).toBe('https://node1.mainnet.hathor.network/v1a/'); - expect(store.getItem('wallet:server')).toBe('https://node1.mainnet.hathor.network/v1a/'); -}); - test('sha256d', () => { expect.hasAssertions(); // sha256d(my-test-data) -> 4f1ba9a4204e97a293b16ead6caced38f6d91d95618b96e261c6332ed24f7894 @@ -68,6 +44,7 @@ test('isTxVoided', async () => { }; }); + // @ts-ignore spy.mockImplementation(mockImplementation); expect(await isTxVoided('0000000f1fbb4bd8a8e71735af832be210ac9a6c1e2081b21faeea3c0f5797f7')).toStrictEqual([ diff --git a/packages/wallet-service/tests/utils.ts b/packages/wallet-service/tests/utils.ts index e07e08b7..8342ef85 100644 --- a/packages/wallet-service/tests/utils.ts +++ b/packages/wallet-service/tests/utils.ts @@ -1,14 +1,14 @@ -import { APIGatewayProxyEvent } from 'aws-lambda'; +import { APIGatewayProxyEvent, SNSEvent, SNSEventRecord } from 'aws-lambda'; import { ServerlessMysql } from 'serverless-mysql'; import { isEqual } from 'lodash'; import { DbSelectResult, TxOutputWithIndex, - FullNodeVersionData, WalletBalanceValue, StringMap, PushProvider, DbTxOutput, + FullNodeApiVersionResponse, } from '@src/types'; import { TxInput } from '@wallet-service/common/src/types'; import { getWalletId } from '@src/utils'; @@ -819,35 +819,50 @@ export const makeGatewayEventWithAuthorizer = ( resource: null, }); -export const addToVersionDataTable = async (mysql: ServerlessMysql, versionData: FullNodeVersionData): Promise => { - const payload = [[ - 1, - versionData.timestamp, - versionData.version, - versionData.network, - versionData.minWeight, - versionData.minTxWeight, - versionData.minTxWeightCoefficient, - versionData.minTxWeightK, - versionData.tokenDepositPercentage, - versionData.rewardSpendMinBlocks, - versionData.maxNumberInputs, - versionData.maxNumberOutputs, - ]]; +export function makeLoadWalletFailedSNSEvent(count: number, xpubkey: string, requestId?: string, errorMessage?: string): SNSEvent { + const event: SNSEventRecord = { + EventVersion: '', + EventSubscriptionArn: '', + EventSource: '', + Sns: { + SignatureVersion: '', + Timestamp: '', + Signature: '', + SigningCertUrl: '', + MessageId: '', + Message: JSON.stringify({ + source: '', + xpubkey, + maxGap: 20, + }), + MessageAttributes: { + RequestID: { Type: 'string', Value: requestId || 'request-id' }, + ErrorMessage: { Type: 'string', Value: errorMessage || 'error-message' }, + }, + Type: '', + UnsubscribeUrl: '', + TopicArn: '', + Subject: '', + Token: '', + }, + }; + + return { + Records: Array(count).fill(event), + }; +} + +export const addToVersionDataTable = async (mysql: ServerlessMysql, timestamp: number, versionData: FullNodeApiVersionResponse): Promise => { + const payload = [[ 1, timestamp, JSON.stringify(versionData) ]]; await mysql.query( - `INSERT INTO \`version_data\`(\`id\`, \`timestamp\`, - \`version\`, \`network\`, - \`min_weight\`, \`min_tx_weight\`, - \`min_tx_weight_coefficient\`, \`min_tx_weight_k\`, - \`token_deposit_percentage\`, \`reward_spend_min_blocks\`, - \`max_number_inputs\`, \`max_number_outputs\`) + `INSERT INTO \`version_data\`(\`id\`, \`timestamp\`, \`data\`) VALUES ?`, [payload], ); }; -export const checkVersionDataTable = async (mysql: ServerlessMysql, versionData: FullNodeVersionData): Promise> => { +export const checkVersionDataTable = async (mysql: ServerlessMysql, versionData: FullNodeApiVersionResponse): Promise> => { // first check the total number of rows in the table let results: DbSelectResult = await mysql.query('SELECT * FROM `version_data`'); @@ -875,19 +890,7 @@ export const checkVersionDataTable = async (mysql: ServerlessMysql, versionData: }; } - const dbVersionData: FullNodeVersionData = { - timestamp: results[0].timestamp as number, - version: results[0].version as string, - network: results[0].network as string, - minWeight: results[0].min_weight as number, - minTxWeight: results[0].min_tx_weight as number, - minTxWeightCoefficient: results[0].min_tx_weight_coefficient as number, - minTxWeightK: results[0].min_tx_weight_k as number, - tokenDepositPercentage: results[0].token_deposit_percentage as number, - rewardSpendMinBlocks: results[0].reward_spend_min_blocks as number, - maxNumberInputs: results[0].max_number_inputs as number, - maxNumberOutputs: results[0].max_number_outputs as number, - }; + const dbVersionData: FullNodeApiVersionResponse = JSON.parse(results[0].data as string); if (Object.entries(dbVersionData).toString() !== Object.entries(versionData).toString()) { return { diff --git a/packages/wallet-service/tests/utils/pushnotification.utils.test.ts b/packages/wallet-service/tests/utils/pushnotification.utils.test.ts index cdcfb8be..3a15d80e 100644 --- a/packages/wallet-service/tests/utils/pushnotification.utils.test.ts +++ b/packages/wallet-service/tests/utils/pushnotification.utils.test.ts @@ -4,7 +4,7 @@ import { mockedAddAlert } from '@tests/utils/alerting.utils.mock'; import { sendMulticastMock, messaging, initFirebaseAdminMock } from '@tests/utils/firebase-admin.mock'; import { logger } from '@tests/winston.mock'; -import { PushNotificationUtils, PushNotificationError, buildFunctionName, FunctionName } from '@src/utils/pushnotification.utils'; +import { PushNotificationUtils, PushNotificationError, FunctionName } from '@src/utils/pushnotification.utils'; import * as pushnotificationUtils from '@src/utils/pushnotification.utils'; import { SendNotificationToDevice } from '@src/types'; import { Severity } from '@wallet-service/common/src/types'; @@ -43,6 +43,7 @@ describe('PushNotificationUtils', () => { afterEach(() => { process.env = initEnv; + jest.resetModules(); }); // test firebase initialization error @@ -57,6 +58,7 @@ describe('PushNotificationUtils', () => { }); // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); const resultMessageOfLastCallToLoggerError = logger.error.mock.calls[0][0]; @@ -71,6 +73,7 @@ describe('PushNotificationUtils', () => { process.env.WALLET_SERVICE_LAMBDA_ENDPOINT = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -89,6 +92,7 @@ describe('PushNotificationUtils', () => { process.env.STAGE = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -107,6 +111,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_PROJECT_ID = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -125,6 +130,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_PRIVATE_KEY_ID = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -143,6 +149,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_PRIVATE_KEY = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -162,6 +169,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_CLIENT_EMAIL = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -181,6 +189,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_CLIENT_ID = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -200,6 +209,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_AUTH_URI = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -219,6 +229,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_TOKEN_URI = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -238,6 +249,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_AUTH_PROVIDER_X509_CERT_URL = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -257,6 +269,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_CLIENT_X509_CERT_URL = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(mockedAddAlert).toHaveBeenLastCalledWith( @@ -276,6 +289,7 @@ describe('PushNotificationUtils', () => { process.env.FIREBASE_PRIVATE_KEY = true as unknown as string; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(logger.error).toHaveBeenLastCalledWith('[ALERT] Error while parsing the env.FIREBASE_PRIVATE_KEY.'); @@ -288,6 +302,7 @@ describe('PushNotificationUtils', () => { process.env.PUSH_ALLOWED_PROVIDERS = ''; // reload module + jest.resetModules(); await import('@src/utils/pushnotification.utils'); expect(logger.error).toHaveBeenLastCalledWith('[ALERT] env.PUSH_ALLOWED_PROVIDERS is empty.'); @@ -427,6 +442,7 @@ describe('PushNotificationUtils', () => { process.env.STAGE = fakeStage; // reload module + jest.resetModules(); const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); const notification = { @@ -469,6 +485,7 @@ describe('PushNotificationUtils', () => { process.env.STAGE = fakeStage; // reload module + jest.resetModules(); const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); const notification = { @@ -499,6 +516,7 @@ describe('PushNotificationUtils', () => { process.env.STAGE = fakeStage; // reload module + jest.resetModules(); const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); const notification = { @@ -526,7 +544,8 @@ describe('PushNotificationUtils', () => { sendMock.mockReturnValueOnce({ StatusCode: 202, }); - const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); + jest.resetModules(); + const { PushNotificationUtils, buildFunctionName } = await import('@src/utils/pushnotification.utils'); const walletMap = buildWalletBalanceValueMap(); const result = await PushNotificationUtils.invokeOnTxPushNotificationRequestedLambda(walletMap); @@ -559,6 +578,7 @@ describe('PushNotificationUtils', () => { jest.clearAllMocks(); // reload module process.env.PUSH_NOTIFICATION_ENABLED = 'false'; + jest.resetModules(); const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); const walletMap = buildWalletBalanceValueMap(); @@ -588,6 +608,7 @@ describe('PushNotificationUtils', () => { // reload module process.env.PUSH_NOTIFICATION_ENABLED = 'true'; + jest.resetModules(); const { PushNotificationUtils } = await import('@src/utils/pushnotification.utils'); const walletMap = buildWalletBalanceValueMap(); diff --git a/packages/wallet-service/tests/ws.utils.test.ts b/packages/wallet-service/tests/ws.utils.test.ts index 6972cde8..7c6e84bb 100644 --- a/packages/wallet-service/tests/ws.utils.test.ts +++ b/packages/wallet-service/tests/ws.utils.test.ts @@ -1,6 +1,6 @@ import { Logger } from 'winston'; import { mockedAddAlert } from '@tests/utils/alerting.utils.mock'; -import { connectionInfoFromEvent, sendMessageToClient } from '@src/ws/utils'; +import { sendMessageToClient } from '@src/ws/utils'; import { Severity } from '@wallet-service/common/src/types'; import { logger } from '@tests/winston.mock'; @@ -45,6 +45,9 @@ import { endWsConnection } from '@src/redis'; test('connectionInfoFromEvent', async () => { expect.hasAssertions(); + const oldValue = process.env.IS_OFFLINE; + process.env.IS_OFFLINE = 'false'; + jest.resetModules(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const event = { @@ -54,16 +57,27 @@ test('connectionInfoFromEvent', async () => { stage: 'test123', }, }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const connInfo = connectionInfoFromEvent(event); - expect(connInfo).toStrictEqual({ id: 'abc123', url: `https://${process.env.WS_DOMAIN}` }); + try { + + const { connectionInfoFromEvent } = await import('@src/ws/utils'); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const connInfo = connectionInfoFromEvent(event); + expect(connInfo).toStrictEqual({ id: 'abc123', url: `https://${process.env.WS_DOMAIN}` }); + } finally { + process.env.IS_OFFLINE = oldValue; + jest.resetModules(); + } }); -test('missing WS_DOMAIN should throw', () => { +test('missing WS_DOMAIN should throw', async () => { expect.hasAssertions(); + const oldOffline = process.env.IS_OFFLINE; + process.env.IS_OFFLINE = 'false'; + const oldDomain = process.env.WS_DOMAIN; delete process.env.WS_DOMAIN; + jest.resetModules(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const event = { @@ -74,16 +88,23 @@ test('missing WS_DOMAIN should throw', () => { }, }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(() => connectionInfoFromEvent(event)).toThrow('Domain not on env variables'); - expect(mockedAddAlert).toHaveBeenCalledWith( - 'Erroed while fetching connection info', - 'Domain not on env variables', - Severity.MINOR, - null, - expect.any(Logger), - ); + try { + const { connectionInfoFromEvent } = await import('@src/ws/utils'); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => connectionInfoFromEvent(event)).toThrow('Domain not on env variables'); + expect(mockedAddAlert).toHaveBeenCalledWith( + 'Erroed while fetching connection info', + 'Domain not on env variables', + Severity.MINOR, + null, + expect.anything(), + ); + } finally { + process.env.WS_DOMAIN = oldDomain; + process.env.IS_OFFLINE = oldOffline; + jest.resetModules(); + } }); describe('sendMessageToClient', () => { diff --git a/yarn.lock b/yarn.lock index f923db7c..27f9cebb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1984,7 +1984,7 @@ __metadata: languageName: node linkType: hard -"@hapi/hoek@npm:^9.0.0": +"@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" checksum: 10/ad83a223787749f3873bce42bd32a9a19673765bf3edece0a427e138859ff729469e68d5fdf9ff6bbee6fb0c8e21bab61415afa4584f527cfc40b59ea1957e70 @@ -2105,7 +2105,7 @@ __metadata: languageName: node linkType: hard -"@hapi/topo@npm:^5.0.0": +"@hapi/topo@npm:^5.0.0, @hapi/topo@npm:^5.1.0": version: 5.1.0 resolution: "@hapi/topo@npm:5.1.0" dependencies: @@ -2871,7 +2871,7 @@ __metadata: languageName: node linkType: hard -"@sideway/address@npm:^4.1.0": +"@sideway/address@npm:^4.1.0, @sideway/address@npm:^4.1.5": version: 4.1.5 resolution: "@sideway/address@npm:4.1.5" dependencies: @@ -2880,7 +2880,7 @@ __metadata: languageName: node linkType: hard -"@sideway/formula@npm:^3.0.0": +"@sideway/formula@npm:^3.0.0, @sideway/formula@npm:^3.0.1": version: 3.0.1 resolution: "@sideway/formula@npm:3.0.1" checksum: 10/8d3ee7f80df4e5204b2cbe92a2a711ca89684965a5c9eb3b316b7051212d3522e332a65a0bb2a07cc708fcd1d0b27fcb30f43ff0bcd5089d7006c7160a89eefe @@ -4214,6 +4214,15 @@ __metadata: languageName: node linkType: hard +"@types/joi@npm:^17.2.3": + version: 17.2.3 + resolution: "@types/joi@npm:17.2.3" + dependencies: + joi: "npm:*" + checksum: 10/f26d0132b4e16d667176a0dce52a013fce1557a9d7a7b68c67e1bdb1ca470a221e638f2ad4566ca8fcfad544ca0845f6e241b3e86cb387ffee07910bad1147bb + languageName: node + linkType: hard + "@types/json-schema@npm:*, @types/json-schema@npm:^7.0.12, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": version: 7.0.13 resolution: "@types/json-schema@npm:7.0.13" @@ -11079,6 +11088,19 @@ __metadata: languageName: node linkType: hard +"joi@npm:*": + version: 17.13.3 + resolution: "joi@npm:17.13.3" + dependencies: + "@hapi/hoek": "npm:^9.3.0" + "@hapi/topo": "npm:^5.1.0" + "@sideway/address": "npm:^4.1.5" + "@sideway/formula": "npm:^3.0.1" + "@sideway/pinpoint": "npm:^2.0.0" + checksum: 10/4c150db0c820c3a52f4a55c82c1fc5e144a5b5f4da9ffebc7339a15469d1a447ebb427ced446efcb9709ab56bd71a06c4c67c9381bc1b9f9ae63fc7c89209bdf + languageName: node + linkType: hard + "joi@npm:17.4.0": version: 17.4.0 resolution: "joi@npm:17.4.0" @@ -16227,6 +16249,7 @@ __metadata: "@middy/http-cors": "npm:2.5.7" "@types/aws-lambda": "npm:8.10.95" "@types/jest": "npm:^29.5.13" + "@types/joi": "npm:^17.2.3" "@types/node": "npm:18.0.4" "@types/redis": "npm:2.8.28" "@typescript-eslint/eslint-plugin": "npm:6.7.4" From 0cb94ac5cfe9eabfd7bd29fa9d38a2f9c2e10e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 18 Mar 2025 12:22:51 -0300 Subject: [PATCH 10/17] chore: review changes --- packages/daemon/src/utils/wallet.ts | 2 +- packages/wallet-service/src/config.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/daemon/src/utils/wallet.ts b/packages/daemon/src/utils/wallet.ts index edae1e1c..ec906770 100644 --- a/packages/daemon/src/utils/wallet.ts +++ b/packages/daemon/src/utils/wallet.ts @@ -68,7 +68,7 @@ export const prepareOutputs = (outputs: EventTxOutput[], tokens: string[]): TxOu tokenData: _output.token_data, }); - let token = '00'; + let token = constants.NATIVE_TOKEN_UID; if (!output.isTokenHTR()) { token = tokens[output.getTokenIndex()]; } diff --git a/packages/wallet-service/src/config.ts b/packages/wallet-service/src/config.ts index 48b2b4b7..a612613a 100644 --- a/packages/wallet-service/src/config.ts +++ b/packages/wallet-service/src/config.ts @@ -23,7 +23,7 @@ export function loadEnvConfig(): EnvironmentConfig { dbEndpoint: process.env.DB_ENDPOINT, dbName: process.env.DB_NAME, dbUser: process.env.DB_USER, - dbPort: parseInt(process.env.DB_PORT, 10), + dbPort: Number.parseInt(process.env.DB_PORT, 10), dbPass: process.env.DB_PASS, redisUrl: process.env.REDIS_URL, redisPassword: process.env.REDIS_PASSWORD, @@ -32,8 +32,8 @@ export function loadEnvConfig(): EnvironmentConfig { pushNotificationEnabled: process.env.PUSH_NOTIFICATION_ENABLED === 'true', pushAllowedProviders: process.env.PUSH_ALLOWED_PROVIDERS, isOffline: process.env.IS_OFFLINE === 'true', - txHistoryMaxCount: parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10), - healthCheckMaximumHeightDifference: Number(process.env.HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE ?? 5), + txHistoryMaxCount: Number.parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10), + healthCheckMaximumHeightDifference: Number.parseInt(process.env.HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE ?? '5', 10), awsRegion: process.env.AWS_REGION, @@ -47,10 +47,10 @@ export function loadEnvConfig(): EnvironmentConfig { firebaseClientX509CertUrl: process.env.FIREBASE_CLIENT_X509_CERT_URL, firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY, - maxLoadWalletRetries: parseInt(process.env.MAX_LOAD_WALLET_RETRIES || '5', 10), + maxLoadWalletRetries: Number.parseInt(process.env.MAX_LOAD_WALLET_RETRIES || '5', 10), logLevel: process.env.LOG_LEVEL || 'info', - createNftMaxRetries: parseInt(process.env.CREATE_NFT_MAX_RETRIES || '3', 10), - warnMaxReorgSize: parseInt(process.env.WARN_MAX_REORG_SIZE || '100', 10), + createNftMaxRetries: Number.parseInt(process.env.CREATE_NFT_MAX_RETRIES || '3', 10), + warnMaxReorgSize: Number.parseInt(process.env.WARN_MAX_REORG_SIZE || '100', 10), }; if (process.env.NODE_ENV === 'test') { From fa74f271c83c74d1e58b41df338485c7a1574b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 18 Mar 2025 12:26:48 -0300 Subject: [PATCH 11/17] chore: lock dev dependency version --- packages/wallet-service/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/wallet-service/package.json b/packages/wallet-service/package.json index a1f296f5..e8cadf78 100644 --- a/packages/wallet-service/package.json +++ b/packages/wallet-service/package.json @@ -45,8 +45,8 @@ }, "devDependencies": { "@types/aws-lambda": "8.10.95", - "@types/jest": "^29.5.13", - "@types/joi": "^17.2.3", + "@types/jest": "29.5.13", + "@types/joi": "17.2.3", "@types/node": "18.0.4", "@typescript-eslint/eslint-plugin": "6.7.4", "@typescript-eslint/parser": "3.3.0", diff --git a/yarn.lock b/yarn.lock index 27f9cebb..ca032f7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4194,7 +4194,7 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:29.5.13, @types/jest@npm:^29.5.13": +"@types/jest@npm:29.5.13": version: 29.5.13 resolution: "@types/jest@npm:29.5.13" dependencies: @@ -4214,7 +4214,7 @@ __metadata: languageName: node linkType: hard -"@types/joi@npm:^17.2.3": +"@types/joi@npm:17.2.3": version: 17.2.3 resolution: "@types/joi@npm:17.2.3" dependencies: @@ -16248,8 +16248,8 @@ __metadata: "@middy/core": "npm:2.5.7" "@middy/http-cors": "npm:2.5.7" "@types/aws-lambda": "npm:8.10.95" - "@types/jest": "npm:^29.5.13" - "@types/joi": "npm:^17.2.3" + "@types/jest": "npm:29.5.13" + "@types/joi": "npm:17.2.3" "@types/node": "npm:18.0.4" "@types/redis": "npm:2.8.28" "@typescript-eslint/eslint-plugin": "npm:6.7.4" From 18568ae76e5bd5d8f051a77324f28da3dc549440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 18 Mar 2025 12:19:40 -0300 Subject: [PATCH 12/17] chore: review changes --- .../20250306170811-node_version_proxy.js | 54 +------------------ packages/wallet-service/src/config.ts | 1 - packages/wallet-service/src/nodeConfig.ts | 11 ++++ packages/wallet-service/src/schemas.ts | 1 - packages/wallet-service/src/types.ts | 1 - .../wallet-service/tests/txProposal.test.ts | 2 +- 6 files changed, 14 insertions(+), 56 deletions(-) diff --git a/db/migrations/20250306170811-node_version_proxy.js b/db/migrations/20250306170811-node_version_proxy.js index 14cba16a..6efca950 100644 --- a/db/migrations/20250306170811-node_version_proxy.js +++ b/db/migrations/20250306170811-node_version_proxy.js @@ -27,57 +27,7 @@ module.exports = { async down (queryInterface, Sequelize) { await queryInterface.dropTable('version_data'); // The "old" table structure was copied from ./20210706175820-create-version-data.js - await queryInterface.createTable('version_data', { - id: { - type: Sequelize.INTEGER.UNSIGNED, - allowNull: false, - primaryKey: true, - defaultValue: 1, - }, - timestamp: { - type: Sequelize.BIGINT.UNSIGNED, - allowNull: false, - }, - version: { - type: Sequelize.STRING(11), - allowNull: false, - }, - network: { - type: Sequelize.STRING(8), - allowNull: false, - }, - min_weight: { - type: Sequelize.FLOAT.UNSIGNED, - allowNull: false, - }, - min_tx_weight: { - type: Sequelize.FLOAT.UNSIGNED, - allowNull: false, - }, - min_tx_weight_coefficient: { - type: Sequelize.FLOAT.UNSIGNED, - allowNull: false, - }, - min_tx_weight_k: { - type: Sequelize.FLOAT.UNSIGNED, - allowNull: false, - }, - token_deposit_percentage: { - type: Sequelize.FLOAT.UNSIGNED, - allowNull: false, - }, - reward_spend_min_blocks: { - type: Sequelize.INTEGER.UNSIGNED, - allowNull: false, - }, - max_number_inputs: { - type: Sequelize.INTEGER.UNSIGNED, - allowNull: false, - }, - max_number_outputs: { - type: Sequelize.INTEGER.UNSIGNED, - allowNull: false, - }, - }); + const { up } = require('./20210706175820-create-version-data'); + await up(queryInterface, Sequelize); } }; diff --git a/packages/wallet-service/src/config.ts b/packages/wallet-service/src/config.ts index a612613a..78589f31 100644 --- a/packages/wallet-service/src/config.ts +++ b/packages/wallet-service/src/config.ts @@ -17,7 +17,6 @@ export function loadEnvConfig(): EnvironmentConfig { serviceName: process.env.SERVICE_NAME, maxAddressGap: Number.parseInt(process.env.MAX_ADDRESS_GAP, 10), voidedTxOffset: Number.parseInt(process.env.VOIDED_TX_OFFSET, 10), - blockRewardLock: Number.parseInt(process.env.BLOCK_REWARD_LOCK, 10), confirmFirstAddress: process.env.CONFIRM_FIRST_ADDRESS === 'true', wsDomain: process.env.WS_DOMAIN, dbEndpoint: process.env.DB_ENDPOINT, diff --git a/packages/wallet-service/src/nodeConfig.ts b/packages/wallet-service/src/nodeConfig.ts index 51543eb1..189506e4 100644 --- a/packages/wallet-service/src/nodeConfig.ts +++ b/packages/wallet-service/src/nodeConfig.ts @@ -13,6 +13,10 @@ import fullnode from '@src/fullnode'; const VERSION_CHECK_MAX_DIFF = 60 * 60; // 1 hour +/** + * Get fullnode version data as an Object exactly as the fullnode sent it. + * Will get from database if the cached version data is valid. + */ export async function getRawFullnodeData(mysql: ServerlessMysql): Promise { const { timestamp, @@ -29,6 +33,9 @@ export async function getRawFullnodeData(mysql: ServerlessMysql): Promise { const data = await getRawFullnodeData(mysql); return convertApiVersionData(data); diff --git a/packages/wallet-service/src/schemas.ts b/packages/wallet-service/src/schemas.ts index be878377..042ba759 100644 --- a/packages/wallet-service/src/schemas.ts +++ b/packages/wallet-service/src/schemas.ts @@ -36,7 +36,6 @@ export const EnvironmentConfigSchema = Joi.object({ serviceName: Joi.string().required(), maxAddressGap: Joi.number().required(), voidedTxOffset: Joi.number().required(), - blockRewardLock: Joi.number().required(), confirmFirstAddress: Joi.boolean().required(), wsDomain: Joi.string().required(), dbEndpoint: Joi.string().required(), diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index aa0fe50e..e2c8d811 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -49,7 +49,6 @@ export interface EnvironmentConfig { serviceName: string; maxAddressGap: number; voidedTxOffset: number; - blockRewardLock: number; // * confirmFirstAddress: boolean; wsDomain: string; dbEndpoint: string; diff --git a/packages/wallet-service/tests/txProposal.test.ts b/packages/wallet-service/tests/txProposal.test.ts index a6d86e22..b42a0c3c 100644 --- a/packages/wallet-service/tests/txProposal.test.ts +++ b/packages/wallet-service/tests/txProposal.test.ts @@ -228,7 +228,7 @@ test('POST /txproposals with too many outputs should fail with ApiError.TOO_MANY await addToWalletTable(mysql, [{ id: 'my-wallet', xpubkey: 'xpubkey', - authXpubkey: 'auth_xpubkey', + authXpubkey: 'auth_xpubkey', status: 'ready', maxGap: 5, createdAt: 10000, From e19170ae97dad418fe3925f4140ce6c7b426569a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Tue, 18 Mar 2025 12:59:02 -0300 Subject: [PATCH 13/17] chore: adapt changes after master update --- .../common/__tests__/utils/nft.utils.test.ts | 18 +++++++++--------- packages/common/src/utils/nft.utils.ts | 8 ++++---- packages/daemon/src/services/index.ts | 2 +- packages/wallet-service/tests/api.test.ts | 1 - 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/common/__tests__/utils/nft.utils.test.ts b/packages/common/__tests__/utils/nft.utils.test.ts index 9e51ba5a..8f32edd4 100644 --- a/packages/common/__tests__/utils/nft.utils.test.ts +++ b/packages/common/__tests__/utils/nft.utils.test.ts @@ -1,4 +1,4 @@ -import hathorLib, { helpersUtils, constants } from '@hathor/wallet-lib'; +import hathorLib, { constants } from '@hathor/wallet-lib'; import { mockedAddAlert } from './alerting.utils.mock'; import { NftUtils } from '@src/utils/nft.utils'; import { Severity } from '@src/types'; @@ -110,7 +110,7 @@ const createTransformedEvent = (fullNodeData = REAL_NFT_EVENT_DATA) => { inputs: fullNodeData.inputs.map((input) => { const tokenIndex = (input.spent_output.token_data & hathorLib.constants.TOKEN_INDEX_MASK) - 1; return { - token: tokenIndex < 0 ? hathorLib.constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], value: input.spent_output.value, token_data: input.spent_output.token_data, script: input.spent_output.script, @@ -127,7 +127,7 @@ const createTransformedEvent = (fullNodeData = REAL_NFT_EVENT_DATA) => { ...output, decoded: output.decoded ? output.decoded : {}, spent_by: null, - token: tokenIndex < 0 ? hathorLib.constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], }; }), }; @@ -532,7 +532,7 @@ describe('transaction transformation compatibility', () => { const tokenIndex = (input.spent_output.token_data & hathorLib.constants.TOKEN_INDEX_MASK) - 1; return { - token: tokenIndex < 0 ? hathorLib.constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], value: input.spent_output.value, token_data: input.spent_output.token_data, script: input.spent_output.script, @@ -549,7 +549,7 @@ describe('transaction transformation compatibility', () => { ...output, decoded: output.decoded ? output.decoded : {}, spent_by: null, - token: tokenIndex < 0 ? hathorLib.constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], }; }), }; @@ -586,11 +586,11 @@ describe('transaction transformation compatibility', () => { // Verify token handling is correct // First input should be HTR token - expect(txFromEvent.inputs[0].token).toBe(hathorLib.constants.HATHOR_TOKEN_CONFIG.uid); + expect(txFromEvent.inputs[0].token).toBe(hathorLib.constants.NATIVE_TOKEN_UID); // First and second outputs should be HTR tokens - expect(txFromEvent.outputs[0].token).toBe(hathorLib.constants.HATHOR_TOKEN_CONFIG.uid); - expect(txFromEvent.outputs[1].token).toBe(hathorLib.constants.HATHOR_TOKEN_CONFIG.uid); + expect(txFromEvent.outputs[0].token).toBe(hathorLib.constants.NATIVE_TOKEN_UID); + expect(txFromEvent.outputs[1].token).toBe(hathorLib.constants.NATIVE_TOKEN_UID); // Third output should be the NFT token expect(txFromEvent.outputs[2].token).toBe(fullNodeData.tokens[0]); @@ -744,7 +744,7 @@ describe('processNftEvent', () => { expect(callArg.outputs.length).toBe(eventData.outputs.length); expect(callArg.outputs[0].spent_by).toBeNull(); expect(callArg.outputs[0].decoded).toEqual({}); - expect(callArg.outputs[0].token).toBe(hathorLib.constants.HATHOR_TOKEN_CONFIG.uid); + expect(callArg.outputs[0].token).toBe(hathorLib.constants.NATIVE_TOKEN_UID); // Verify the lambda was invoked with the correct parameters expect(invokeNftLambdaSpy).toHaveBeenCalledTimes(1); diff --git a/packages/common/src/utils/nft.utils.ts b/packages/common/src/utils/nft.utils.ts index 22ec563a..1655edd8 100644 --- a/packages/common/src/utils/nft.utils.ts +++ b/packages/common/src/utils/nft.utils.ts @@ -9,8 +9,8 @@ import { LambdaClient, InvokeCommand, InvokeCommandOutput } from '@aws-sdk/clien import { addAlert } from './alerting.utils'; import { Severity } from '../types'; import { Network, constants, CreateTokenTransaction, helpersUtils } from '@hathor/wallet-lib'; -// @ts-ignore -import type { HistoryTransaction } from '@hathor/wallet-lib'; +// FIXME: import from lib path on HathorLib +import type { HistoryTransaction } from '@hathor/wallet-lib/lib/models/types'; import { Logger } from 'winston'; import { FullNodeTransaction, FullNodeInput, FullNodeOutput } from '../types'; @@ -251,7 +251,7 @@ export class NftUtils { return { tx_id: input.tx_id, index: input.index, - token: tokenIndex < 0 ? constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], token_data: input.spent_output.token_data, value: input.spent_output.value, script: input.spent_output.script, @@ -268,7 +268,7 @@ export class NftUtils { value: output.value, token_data: output.token_data, script: output.script, - token: tokenIndex < 0 ? constants.HATHOR_TOKEN_CONFIG.uid : fullNodeData.tokens[tokenIndex], + token: tokenIndex < 0 ? constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], decoded: output.decoded || {}, spent_by: null, }; diff --git a/packages/daemon/src/services/index.ts b/packages/daemon/src/services/index.ts index 38378992..39e82ab9 100644 --- a/packages/daemon/src/services/index.ts +++ b/packages/daemon/src/services/index.ts @@ -217,7 +217,7 @@ export const handleVertexAccepted = async (context: Context, _event: Event) => { const txOutputs: TxOutputWithIndex[] = prepareOutputs(outputs, tokens); const txInputs: TxInput[] = prepareInputs(inputs, tokens); - let heightlock = null; + let heightlock: number|null = null; if (isBlock(version)) { if (typeof height !== 'number' && !height) { throw new Error('Block with no height set in metadata.'); diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 7ea2c8bd..260457f7 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -60,7 +60,6 @@ import { import fullnode from '@src/fullnode'; import { getHealthcheck } from '@src/api/healthcheck'; import { Severity } from '@wallet-service/common'; -import { ErrorMessages } from '@hathor/wallet-lib/lib/errorMessages'; // Monkey patch bitcore-lib From 2477efa96f7bd06dc38e738d3152eea514918225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 19 Mar 2025 16:26:30 -0300 Subject: [PATCH 14/17] chore: do not directly alter constants --- .../common/__tests__/utils/nft.utils.test.ts | 81 ++++++------------- packages/common/src/utils/nft.utils.ts | 3 - 2 files changed, 24 insertions(+), 60 deletions(-) diff --git a/packages/common/__tests__/utils/nft.utils.test.ts b/packages/common/__tests__/utils/nft.utils.test.ts index 8f32edd4..52b8771a 100644 --- a/packages/common/__tests__/utils/nft.utils.test.ts +++ b/packages/common/__tests__/utils/nft.utils.test.ts @@ -162,7 +162,6 @@ describe('shouldInvokeNftHandlerForTx', () => { expect(process.env.NFT_AUTO_REVIEW_ENABLED).not.toStrictEqual('true'); // Execution - // @ts-ignore const shouldInvoke = NftUtils.shouldInvokeNftHandlerForTx(tx, network, logger); // Since NFT_AUTO_REVIEW_ENABLED is false, the function should return false @@ -486,10 +485,6 @@ describe('transaction transformation compatibility', () => { it('should correctly transform fullNodeData to a format compatible with shouldInvokeNftHandlerForTx', () => { expect.hasAssertions(); - // Save original value and mock for this test - const originalVersion = constants.CREATE_TOKEN_TX_VERSION; - constants.CREATE_TOKEN_TX_VERSION = 2; - // Set up environment variables const originalEnv = process.env; process.env = { ...originalEnv, NFT_AUTO_REVIEW_ENABLED: 'true' }; @@ -561,7 +556,6 @@ describe('transaction transformation compatibility', () => { expect(isNftTransactionSpy).toHaveBeenCalledTimes(1); } finally { // Restore original values - constants.CREATE_TOKEN_TX_VERSION = originalVersion; process.env = originalEnv; } }); @@ -569,10 +563,6 @@ describe('transaction transformation compatibility', () => { it('should correctly process a real event from production', () => { expect.hasAssertions(); - // Save original value and mock - const originalVersion = constants.CREATE_TOKEN_TX_VERSION; - constants.CREATE_TOKEN_TX_VERSION = 2; - // Set up environment variables const originalEnv = process.env; process.env = { ...originalEnv, NFT_AUTO_REVIEW_ENABLED: 'true' }; @@ -611,7 +601,6 @@ describe('transaction transformation compatibility', () => { } finally { // Restore environment and constants process.env = originalEnv; - constants.CREATE_TOKEN_TX_VERSION = originalVersion; } }); }); @@ -620,7 +609,6 @@ describe('processNftEvent', () => { let originalEnv: NodeJS.ProcessEnv; let invokeNftLambdaSpy: jest.SpyInstance; let shouldInvokeSpy: jest.SpyInstance; - let originalVersion: number; beforeEach(() => { // Save original env @@ -632,10 +620,6 @@ describe('processNftEvent', () => { AWS_REGION: 'us-east-1' }; - // Save original constants - originalVersion = constants.CREATE_TOKEN_TX_VERSION; - constants.CREATE_TOKEN_TX_VERSION = 2; - // Set up spies invokeNftLambdaSpy = jest.spyOn(NftUtils, 'invokeNftHandlerLambda').mockResolvedValue(); shouldInvokeSpy = jest.spyOn(NftUtils, 'shouldInvokeNftHandlerForTx').mockReturnValue(true); @@ -649,7 +633,6 @@ describe('processNftEvent', () => { // Clean up process.env = originalEnv; jest.restoreAllMocks(); - constants.CREATE_TOKEN_TX_VERSION = originalVersion; }); it('should invoke the NFT handler for a valid NFT event', async () => { @@ -829,47 +812,36 @@ describe('processNftEvent', () => { it('should return false for non-token-creation transactions', async () => { expect.hasAssertions(); - // Store original value to restore later - const originalVersion = constants.CREATE_TOKEN_TX_VERSION; - - try { - // Set CREATE_TOKEN_TX_VERSION to a specific value - constants.CREATE_TOKEN_TX_VERSION = 1; - - // Use the real event data constant with a non-matching version - const eventData = { - ...REAL_NFT_EVENT_DATA, - version: 2 // Different from CREATE_TOKEN_TX_VERSION - }; + // Use the real event data constant with a non-matching version + const eventData = { + ...REAL_NFT_EVENT_DATA, + version: 1 // Different from CREATE_TOKEN_TX_VERSION + }; - // Mock network - const mockNetwork = { name: 'testnet' }; + // Mock network + const mockNetwork = { name: 'testnet' }; - // Call the method - const result = await NftUtils.processNftEvent( - eventData, - 'test-stage', - mockNetwork as unknown as hathorLib.Network, - logger - ); + // Call the method + const result = await NftUtils.processNftEvent( + eventData, + 'test-stage', + mockNetwork as unknown as hathorLib.Network, + logger + ); - // Verify result is false (non-token-creation tx) - expect(result).toBe(false); + // Verify result is false (non-token-creation tx) + expect(result).toBe(false); - // Verify shouldInvokeNftHandlerForTx was NOT called - expect(shouldInvokeSpy).not.toHaveBeenCalled(); + // Verify shouldInvokeNftHandlerForTx was NOT called + expect(shouldInvokeSpy).not.toHaveBeenCalled(); - // Verify the lambda was NOT invoked - expect(invokeNftLambdaSpy).not.toHaveBeenCalled(); + // Verify the lambda was NOT invoked + expect(invokeNftLambdaSpy).not.toHaveBeenCalled(); - // Verify debug message was logged - expect(logger.debug).toHaveBeenCalledWith( - expect.stringContaining('not a token creation transaction') - ); - } finally { - // Restore original value - constants.CREATE_TOKEN_TX_VERSION = originalVersion; - } + // Verify debug message was logged + expect(logger.debug).toHaveBeenCalledWith( + expect.stringContaining('not a token creation transaction') + ); }); }); @@ -889,10 +861,6 @@ it('should perform full NFT processing with real event data and no mocks', async AWS_REGION: 'us-east-1' }; - // Save original constants - const originalVersion = constants.CREATE_TOKEN_TX_VERSION; - constants.CREATE_TOKEN_TX_VERSION = 2; - // Mock the Lambda client to prevent actual AWS calls const mockSend = jest.fn().mockImplementation(() => { return Promise.resolve({ StatusCode: 202 }); @@ -964,7 +932,6 @@ it('should perform full NFT processing with real event data and no mocks', async } finally { // Restore original values process.env = originalEnv; - constants.CREATE_TOKEN_TX_VERSION = originalVersion; } } finally { // Restore spies diff --git a/packages/common/src/utils/nft.utils.ts b/packages/common/src/utils/nft.utils.ts index 1655edd8..18258e7b 100644 --- a/packages/common/src/utils/nft.utils.ts +++ b/packages/common/src/utils/nft.utils.ts @@ -37,9 +37,6 @@ export class NftUtils { /** * Returns if the transaction in the parameter is a NFT Creation. - * @param {Transaction} tx - * @returns {boolean} - * * TODO: Remove the logger param after we unify the logger from both projects */ static isTransactionNFTCreation(tx: HistoryTransaction, network: Network, logger: Logger): boolean { From 4e8d9bf264c1226e266f58b08803abfef02058c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 19 Mar 2025 16:46:28 -0300 Subject: [PATCH 15/17] chore: fix test typing for mocked data --- .../common/__tests__/events/nftCreationTx.ts | 15 +++--- .../common/__tests__/utils/nft.utils.test.ts | 51 ++++++------------- packages/common/src/utils/nft.utils.ts | 18 +++++-- 3 files changed, 37 insertions(+), 47 deletions(-) diff --git a/packages/common/__tests__/events/nftCreationTx.ts b/packages/common/__tests__/events/nftCreationTx.ts index 0c91445b..4fcd98bb 100644 --- a/packages/common/__tests__/events/nftCreationTx.ts +++ b/packages/common/__tests__/events/nftCreationTx.ts @@ -12,7 +12,9 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { Context } from 'aws-lambda'; -import { Transaction, TxOutput } from '../../src/types'; +import { TxOutput } from '../../src/types'; +// FIXME: import from lib path on HathorLib +import { HistoryTransaction } from '@hathor/wallet-lib/lib/models/types'; /** * A sample transaction for a NFT creation, as obtained by a wallet's history methods @@ -115,12 +117,12 @@ export const nftCreationTx = { /** * Gets a copy of the `nftCreationTx` in the Wallet Service's Transaction format. */ -export function getTransaction(): Transaction { - const result = { +export function getTransaction(): HistoryTransaction { + return { tx_id: nftCreationTx.tx_id, nonce: 1, timestamp: nftCreationTx.timestamp, - signal_bits: nftCreationTx.signal_bits, + signalBits: nftCreationTx.signal_bits, version: nftCreationTx.version, weight: nftCreationTx.weight, parents: nftCreationTx.parents, @@ -150,12 +152,11 @@ export function getTransaction(): Transaction { token_data: o.token_data, locked: false, })) as TxOutput[], - height: 8, token_name: nftCreationTx.token_name, token_symbol: nftCreationTx.token_symbol, + is_voided: nftCreationTx.is_voided, + tokens: nftCreationTx.tokens, }; - - return result; } /** diff --git a/packages/common/__tests__/utils/nft.utils.test.ts b/packages/common/__tests__/utils/nft.utils.test.ts index 52b8771a..6b52f4b9 100644 --- a/packages/common/__tests__/utils/nft.utils.test.ts +++ b/packages/common/__tests__/utils/nft.utils.test.ts @@ -1,7 +1,7 @@ -import hathorLib, { constants } from '@hathor/wallet-lib'; +import hathorLib, { constants, Network } from '@hathor/wallet-lib'; import { mockedAddAlert } from './alerting.utils.mock'; import { NftUtils } from '@src/utils/nft.utils'; -import { Severity } from '@src/types'; +import { FullNodeTransaction, Severity } from '@src/types'; import { getHandlerContext, getTransaction } from '../events/nftCreationTx'; import { LambdaClient as LambdaClientMock, @@ -104,33 +104,7 @@ const REAL_NFT_EVENT_DATA = { // Create the transformed version of the event as it would be processed const createTransformedEvent = (fullNodeData = REAL_NFT_EVENT_DATA) => { - return { - ...fullNodeData, - tx_id: fullNodeData.hash, - inputs: fullNodeData.inputs.map((input) => { - const tokenIndex = (input.spent_output.token_data & hathorLib.constants.TOKEN_INDEX_MASK) - 1; - return { - token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], - value: input.spent_output.value, - token_data: input.spent_output.token_data, - script: input.spent_output.script, - decoded: { - ...(input.spent_output.decoded || {}), - }, - tx_id: input.tx_id, - index: input.index - }; - }), - outputs: fullNodeData.outputs.map((output) => { - const tokenIndex = (output.token_data & hathorLib.constants.TOKEN_INDEX_MASK) - 1; - return { - ...output, - decoded: output.decoded ? output.decoded : {}, - spent_by: null, - token: tokenIndex < 0 ? hathorLib.constants.NATIVE_TOKEN_UID : fullNodeData.tokens[tokenIndex], - }; - }), - }; + return NftUtils.transformFullNodeTxForNftDetection(fullNodeData); }; describe('shouldInvokeNftHandlerForTx', () => { @@ -155,7 +129,7 @@ describe('shouldInvokeNftHandlerForTx', () => { const tx = getTransaction(); const network = { name: 'testnet', - }; + } as unknown as Network; // Explicitly disable the feature process.env.NFT_AUTO_REVIEW_ENABLED = 'false'; @@ -490,7 +464,7 @@ describe('transaction transformation compatibility', () => { process.env = { ...originalEnv, NFT_AUTO_REVIEW_ENABLED: 'true' }; try { - const fullNodeData = { + const fullNodeData: FullNodeTransaction = { hash: 'test-hash', version: constants.CREATE_TOKEN_TX_VERSION, token_name: 'Test NFT', @@ -505,7 +479,8 @@ describe('transaction transformation compatibility', () => { script: 'script1', decoded: { type: 'P2PKH', - address: 'addr1' + address: 'addr1', + timelock: null } } }], @@ -515,12 +490,18 @@ describe('transaction transformation compatibility', () => { script: 'script2', decoded: { type: 'P2PKH', - address: 'addr2' + address: 'addr2', + timelock: null, } - }] + }], + nonce: 0, + signal_bits: 1, + timestamp: 0, + weight: 18.2, }; - const txFromEvent = { + const txFromEvent = NftUtils.transformFullNodeTxForNftDetection(fullNodeData); + const txFromEvent2 = { ...fullNodeData, tx_id: fullNodeData.hash, inputs: fullNodeData.inputs.map((input) => { diff --git a/packages/common/src/utils/nft.utils.ts b/packages/common/src/utils/nft.utils.ts index 18258e7b..92cae4fb 100644 --- a/packages/common/src/utils/nft.utils.ts +++ b/packages/common/src/utils/nft.utils.ts @@ -190,7 +190,7 @@ export class NftUtils { static async processNftEvent( eventData: FullNodeTransaction, stage: string, - network: unknown, + network: Network, logger: Logger ): Promise { // Early return if NFT auto review is disabled @@ -231,13 +231,10 @@ export class NftUtils { */ static transformFullNodeTxForNftDetection(fullNodeData: FullNodeTransaction): HistoryTransaction { // Create a new object with the required properties - const transformedTx: any = { - hash: fullNodeData.hash, + let transformedTx: HistoryTransaction = { tx_id: fullNodeData.hash, // Add tx_id for compatibility version: fullNodeData.version, tokens: fullNodeData.tokens, - token_name: fullNodeData.token_name, - token_symbol: fullNodeData.token_symbol, inputs: fullNodeData.inputs.map((input: FullNodeInput) => { // Extract the token index from token_data using hathor's TOKEN_INDEX_MASK // The token_data field contains both the token index and other flags @@ -270,8 +267,19 @@ export class NftUtils { spent_by: null, }; }), + signalBits: fullNodeData.signal_bits, + weight: fullNodeData.weight, + timestamp: fullNodeData.timestamp, + is_voided: !!fullNodeData.voided, + nonce: fullNodeData.nonce, + parents: fullNodeData.parents ?? [], }; + if (fullNodeData.token_name && fullNodeData.token_symbol) { + transformedTx.token_name = fullNodeData.token_name; + transformedTx.token_symbol = fullNodeData.token_symbol; + } + return transformedTx; } } From 4492ec3c87c8e0232417f9eab6e6d31bf2229ddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Wed, 19 Mar 2025 16:57:39 -0300 Subject: [PATCH 16/17] chore: fix typo --- db/migrations/20250306170811-node_version_proxy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/migrations/20250306170811-node_version_proxy.js b/db/migrations/20250306170811-node_version_proxy.js index 6efca950..9e5e0e65 100644 --- a/db/migrations/20250306170811-node_version_proxy.js +++ b/db/migrations/20250306170811-node_version_proxy.js @@ -3,7 +3,7 @@ /** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) { - // Dropping the table and recreating it is easier than deleting the fields then addind the new + // Dropping the table and recreating it is easier than deleting the fields then adding the new // one, there is also no danger of losing any data since this is fetched from the fullnode await queryInterface.dropTable('version_data'); await queryInterface.createTable('version_data', { From dfd52bb68eab82ad83b84f418df9167899cb54c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Mon, 24 Mar 2025 20:30:53 -0300 Subject: [PATCH 17/17] tests: make value always the expected on the config test --- packages/wallet-service/tests/env.test.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/wallet-service/tests/env.test.ts b/packages/wallet-service/tests/env.test.ts index 076985d7..5b724c60 100644 --- a/packages/wallet-service/tests/env.test.ts +++ b/packages/wallet-service/tests/env.test.ts @@ -3,10 +3,18 @@ import config, { loadEnvConfig } from '@src/config'; test('Configuration should load correctly during tests', () => { expect.hasAssertions(); - const loadedConfig = loadEnvConfig(); - expect(loadedConfig).toStrictEqual(config); + const oldValue = process.env.CONFIRM_FIRST_ADDRESS; + try { + process.env.CONFIRM_FIRST_ADDRESS = 'true'; + jest.resetModules(); + const loadedConfig = loadEnvConfig(); + expect(loadedConfig).toStrictEqual(config); - expect(config.confirmFirstAddress).toEqual(true); + expect(config.confirmFirstAddress).toEqual(true); + } finally { + process.env.CONFIRM_FIRST_ADDRESS = oldValue; + jest.resetModules(); + } }); test('loadEnvConfig should get the config from the env', () => {