From 50adc45bed41b9ba9ffefe151009e814e9708c04 Mon Sep 17 00:00:00 2001 From: martiliones Date: Tue, 15 Feb 2022 20:50:21 +0600 Subject: [PATCH 01/31] style: use new code style --- .editorconfig | 15 -------- .eslintrc.js | 70 ++++++++++++++-------------------- .gitignore | 1 + .husky/commit-msg | 4 ++ .husky/pre-commit | 4 ++ CONTRIBUTING.md | 89 ++++++++++++++++++++++++++++++++++++++++++++ commitlint.config.js | 3 ++ package.json | 19 ++++++++-- 8 files changed, 144 insertions(+), 61 deletions(-) delete mode 100644 .editorconfig create mode 100755 .husky/commit-msg create mode 100755 .husky/pre-commit create mode 100644 CONTRIBUTING.md create mode 100644 commitlint.config.js diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 6f445f1..0000000 --- a/.editorconfig +++ /dev/null @@ -1,15 +0,0 @@ -# Install EditorConfig Plugin on your IDE for one coding style -# EditorConfig is awesome: http://EditorConfig.org - -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -indent_style = tab -tab_width = 4 -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js index 6dd62da..b349f4d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,43 +1,29 @@ module.exports = { - "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module" - }, - "rules": { - "semi": "warn", // обязательно ; - "semi-spacing": ["error", {"before": false, "after": true}], - "indent": ["error", "tab"], - "space-infix-ops": "error",// отступы вокруг + - * / = и тд - "eqeqeq": "error", // обязательно === и !== (нельзя == и !=) - // "no-eq-null": "error", // обязательно === и !== (нельзя == и !=) но тоько в отношении null - "curly": "error", // проверка шаблонов `${name}` - // "space-before-function-paren": [ // отступ до и после function - // "error", { - // "anonymous": "always", - // "named": "always", - // "asyncArrow": "ignore" - // } - // ], - "key-spacing": ["error", { "mode": "strict" }], // оформление обЪекта - "space-in-parens": ["error", "never"], // запрет отступов ( a,b) - "computed-property-spacing": ["error", "never"], // запрет лишних отступов в выражениях a[ i] - "array-bracket-spacing": ["error", "never"], - "no-multi-spaces": "error", // запрет лишних пробелов var a = 2 - "no-sparse-arrays": "warn", // предупреждение при дырке в массиве - "no-mixed-spaces-and-tabs": "error", // нельзя миксовать табы и пробелы - "keyword-spacing": ["error", { "after": true }], - "comma-spacing": ["error", { "before": false, "after": true }], // отступ после запятой, а перед нельзя - "no-undef":"error", - "array-callback-return": "error" // коллбек методов массива типа arr.map arr.filter должны иметь return в коллбеке - }, - "env": { - "browser": true, - "node": true - }, - "globals": { - "Vue":true, - "Symbol":true, - "Promise":true, - }, - "plugins": [] -} + env: { + commonjs: true, + es2021: true, + browser: true, + node: true, + 'jest/globals': true, + }, + extends: ['eslint:recommended', 'google'], + plugins: ['jest'], + parserOptions: { + ecmaVersion: 12, + }, + rules: { + 'max-len': [ + 'error', + { + code: 200, + ignoreTrailingComments: true, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + }, + ], + 'require-jsdoc': 'off', + 'quote-props': 'off', + }, +}; diff --git a/.gitignore b/.gitignore index f081cae..977a377 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ logs/ .vscode/ test.js package-lock.json +.editorconfig diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100755 index 0000000..7fed485 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npx --no -- commitlint --edit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..20d0d06 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run lint diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..82c6c1a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing Guide + +Before submitting your contribution, please make sure to take a moment and read through the following guidelines: + +- [Pull Request Guidelines](#pull-request-guidelines) +- [Development Setup](#development-setup) +- [Scripts](#scripts) +- [Project Structure](#project-structure) +- [Contributing Tests](#contributing-tests) + +## Pull Request Guidelines + +- Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch. + +- If adding a new feature add accompanying test case. + +- It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. + +- Make sure tests pass! + +- Commit messages must follow the [commit message convention](./commit-convention.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)). + +- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)).. + +## Development Setup + +You will need [Node.js](https://nodejs.org) **version 16+**. + +After cloning the repo, run: + +```bash +$ npm i # install the dependencies of the project +``` + +A high level overview of tools used: + +- [Jest](https://jestjs.io/) for unit testing +- [Prettier](https://prettier.io/) for code formatting + +## Scripts + +### `npm run lint` + +The `lint` script runs linter. + +```bash +# lint files +$ npm run lint +# fix linter errors +$ npm run lint:fix +``` + +### `npm run test` + +The `test` script simply calls the `jest` binary, so all [Jest CLI Options](https://jestjs.io/docs/en/cli) can be used. Some examples: + +```bash +# run all tests +$ npm run test + +# run all tests under the runtime-core package +$ npm run test -- runtime-core + +# run tests in a specific file +$ npm run test -- fileName + +# run a specific test in a specific file +$ npm run test -- fileName -t 'test name' +``` + +## Project Structure + +- **`src`**: contains the source code + + - **`api`**: contains group of methods and methods for the API. + + - **`helpers`**: contains utilities shared across the entire codebase. + + - **`tests`**: contains tests for the helpers directory. + + - **`tests`**: contains tests for the src directory. + +## Contributing Tests + +Unit tests are collocated with the code being tested inside directories named `tests`. Consult the [Jest docs](https://jestjs.io/docs/en/using-matchers) and existing test cases for how to write new test specs. Here are some additional guidelines: + +- Use the minimal API needed for a test case. For example, if a test can be written without involving the reactivity system or a component, it should be written so. This limits the test's exposure to changes in unrelated parts and makes it more stable. + +- Only use platform-specific runtimes if the test is asserting platform-specific behavior. diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 0000000..c34aa79 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'] +}; diff --git a/package.json b/package.json index 7a7c87e..d9759a3 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,18 @@ "description": "REST API for ADAMANT Blockchain", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest", + "lint": "eslint .eslintrc.js", + "lint:fix": "eslint --fix .eslintrc.js", + "prepare": "husky install" }, "author": "RomanS, Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { + "@liskhq/lisk-cryptography": "3.2.0", "axios": "^0.25.0", "bignumber.js": "^9.0.2", - "bitcoinjs-lib": "^5.2.0", + "bitcoinjs-lib": "^5.2.0", "bitcore-mnemonic": "^8.25.25", "bytebuffer": "^5.0.1", "coininfo": "^5.1.0", @@ -19,7 +23,6 @@ "ethereumjs-util": "^7.1.4", "hdkey": "^2.0.1", "pbkdf2": "^3.1.2", - "@liskhq/lisk-cryptography": "3.2.0", "socket.io-client": "^2.4.0", "sodium-browserify-tweetnacl": "^0.2.6" }, @@ -58,5 +61,13 @@ "bugs": { "url": "https://github.com/Adamant-im/adamant-api-jsclient/issues" }, - "homepage": "https://github.com/Adamant-im/adamant-api-jsclient#readme" + "homepage": "https://github.com/Adamant-im/adamant-api-jsclient#readme", + "devDependencies": { + "@commitlint/cli": "^16.2.1", + "@commitlint/config-conventional": "^16.2.1", + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-jest": "^26.1.0", + "husky": "^7.0.4" + } } From b278e79b6b823627cea9f54f9e2c27cccd23f7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=8C=EF=B8=8F?= Date: Tue, 15 Feb 2022 21:16:41 +0600 Subject: [PATCH 02/31] Update author Co-authored-by: adamant-al <33592982+adamant-al@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9759a3..9511d56 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint:fix": "eslint --fix .eslintrc.js", "prepare": "husky install" }, - "author": "RomanS, Aleksei Lebedev (https://adamant.im)", + "author": "ADAMANT Foundation (https://adamant.im)", "license": "GPL-3.0", "dependencies": { "@liskhq/lisk-cryptography": "3.2.0", From c4be7b10ab05ae70a1e25f57a3bc98643b2c707d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=8C=EF=B8=8F?= Date: Tue, 15 Feb 2022 21:24:16 +0600 Subject: [PATCH 03/31] docs(contributing): remove extra point Co-authored-by: adamant-al <33592982+adamant-al@users.noreply.github.com> --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82c6c1a..e7d6562 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Before submitting your contribution, please make sure to take a moment and read - Commit messages must follow the [commit message convention](./commit-convention.md). Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)). -- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)).. +- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [husky](https://github.com/typicode/husky)). ## Development Setup From 73e4b6892a1cd9edae1b8394486148dd750761fc Mon Sep 17 00:00:00 2001 From: martiliones Date: Tue, 15 Feb 2022 21:33:01 +0600 Subject: [PATCH 04/31] docs: add rule for the PR guidelines --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e7d6562..f45a23b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,8 @@ Before submitting your contribution, please make sure to take a moment and read ## Pull Request Guidelines +- The master branch is just a snapshot of the latest stable release. All development should be done in dedicated branches. Do not submit PRs against the master branch. + - Checkout a topic branch from a base branch, e.g. `master`, and merge back against that branch. - If adding a new feature add accompanying test case. From a115c4133b7b15580bccc6e8a593538f5ec6543b Mon Sep 17 00:00:00 2001 From: martiliones Date: Tue, 22 Mar 2022 05:12:19 +0600 Subject: [PATCH 05/31] feat(options): add the option to disable health checks at startup --- helpers/healthCheck.js | 27 +++++++++++++++------------ index.js | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/helpers/healthCheck.js b/helpers/healthCheck.js index 71b57e7..f68c504 100644 --- a/helpers/healthCheck.js +++ b/helpers/healthCheck.js @@ -7,7 +7,7 @@ const dnsPromises = require('dns').promises; const CHECK_NODES_INTERVAL = 60 * 5 * 1000; // Update active nodes every 5 minutes const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced -module.exports = (nodes) => { +module.exports = (nodes, checkHealthAtStartup = true) => { isCheckingNodes = false; nodesList = nodes; @@ -16,29 +16,32 @@ module.exports = (nodes) => { /** * Updates active nodes. If nodes are already updating, returns Promise of previous call - * @returns {Promise} Call changeNodes().then to do something when update complete - */ - function changeNodes (isPlannedUpdate = false) { + * @returns {Promise} Call changeNodes().then to do something when update complete + */ + function changeNodes(isPlannedUpdate = false) { if (!isCheckingNodes) { changeNodesPromise = new Promise(async (resolve) => { if (!isPlannedUpdate) { logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); } - await checkNodes(isPlannedUpdate? false : true) + await checkNodes(isPlannedUpdate ? false : true) resolve(true) }); } return changeNodesPromise } - changeNodes(true) - setInterval(() => { changeNodes(true) }, CHECK_NODES_INTERVAL); + + if (checkHealthAtStartup) { + changeNodes(true) + setInterval(() => { changeNodes(true) }, CHECK_NODES_INTERVAL); + } return { /** - * @returns {string} Current active node, f. e. http://88.198.156.44:36666 - */ + * @returns {string} Current active node, f. e. http://88.198.156.44:36666 + */ node: () => { return activeNode; }, @@ -145,10 +148,10 @@ async function checkNodes(forceChangeActiveNode) { this.liveNodes.sort((a, b) => a.ping - b.ping); if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) - this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length-1)].node // Use random node from which are synced + this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node // Use random node from which are synced else this.activeNode = biggestGroup[0].node; // Use node with minimum ping among which are synced - + } socket.reviseConnection(this.liveNodes); let unavailableCount = this.nodesList.length - this.liveNodes.length; @@ -159,7 +162,7 @@ async function checkNodes(forceChangeActiveNode) { if (outOfSyncCount) nodesInfoString += `, ${outOfSyncCount} nodes are not synced` logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); - + } } catch (e) { diff --git a/index.js b/index.js index daba8ec..7a2e1ef 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,7 @@ const logger = require('./helpers/logger'); module.exports = (params, log) => { log = log || console; logger.initLogger(params.logLevel, log); - const nodeManager = healthCheck(params.node); + const nodeManager = healthCheck(params.node, params.checkHealthAtStartup); return { get: get(nodeManager), From 09388bdd56417b26634f0e4fee13f021507e304b Mon Sep 17 00:00:00 2001 From: gost1k Date: Wed, 23 Mar 2022 18:37:29 +0300 Subject: [PATCH 06/31] feat: add axios instance --- groups/get.js | 4 ++-- groups/newDelegate.js | 2 +- groups/sendMessage.js | 12 ++++++------ groups/sendTokens.js | 6 +++--- groups/voteForDelegate.js | 2 +- helpers/axiosClient.js | 4 ++++ helpers/healthCheck.js | 6 +++--- 7 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 helpers/axiosClient.js diff --git a/groups/get.js b/groups/get.js index 507c522..83f8e81 100644 --- a/groups/get.js +++ b/groups/get.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const logger = require('../helpers/logger'); const validator = require('../helpers/validator'); @@ -35,7 +35,7 @@ module.exports = (nodeManager) => { function trimAny(str, chars) { if (!str || typeof str !== 'string') return '' - let start = 0, + let start = 0, end = str.length; while(start < end && chars.indexOf(str[start]) >= 0) ++start; diff --git a/groups/newDelegate.js b/groups/newDelegate.js index 1906f9a..b3ddb73 100644 --- a/groups/newDelegate.js +++ b/groups/newDelegate.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const logger = require('../helpers/logger'); const keys = require('../helpers/keys'); const constants = require('../helpers/constants'); diff --git a/groups/sendMessage.js b/groups/sendMessage.js index 991628c..d230e65 100644 --- a/groups/sendMessage.js +++ b/groups/sendMessage.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const logger = require('../helpers/logger'); const keys = require('../helpers/keys'); const constants = require('../helpers/constants'); @@ -34,7 +34,7 @@ module.exports = (nodeManager) => { if (!validator.validatePassPhrase(passPhrase)) return validator.badParameter('passPhrase') - + keyPair = keys.createKeypairFromPassPhrase(passPhrase); if (!validator.validateAdmAddress(addressOrPublicKey)) { @@ -59,7 +59,7 @@ module.exports = (nodeManager) => { message_type = 2; if (message_type === 'signal') message_type = 3; - + if (!validator.validateMessageType(message_type)) return validator.badParameter('message_type', message_type) @@ -72,13 +72,13 @@ module.exports = (nodeManager) => { recipientId: address, message_type }; - + if (amount) { if (isAmountInADM) { amountInSat = validator.AdmToSats(amount) } else { amountInSat = amount - } + } if (!validator.validateIntegerAmount(amountInSat)) return validator.badParameter('amount', amount) data.amount = amountInSat; @@ -126,7 +126,7 @@ module.exports = (nodeManager) => { logger.warn(`${logMessage} No more attempts, returning error.`); return validator.formatRequestResults(error, false) }) - + } catch (e) { return new Promise((resolve, reject) => { diff --git a/groups/sendTokens.js b/groups/sendTokens.js index 6e5da0d..8a14a0d 100644 --- a/groups/sendTokens.js +++ b/groups/sendTokens.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const logger = require('../helpers/logger'); const keys = require('../helpers/keys'); const constants = require('../helpers/constants'); @@ -28,7 +28,7 @@ module.exports = (nodeManager) => { if (!validator.validatePassPhrase(passPhrase)) return validator.badParameter('passPhrase') - + const keyPair = keys.createKeypairFromPassPhrase(passPhrase); if (!validator.validateAdmAddress(addressOrPublicKey)) { @@ -46,7 +46,7 @@ module.exports = (nodeManager) => { publicKey = ''; address = addressOrPublicKey } - + if (isAmountInADM) { amountInSat = validator.AdmToSats(amount) } else { diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 1d37d67..0fae4dc 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const get = require('./get'); const logger = require('../helpers/logger'); const keys = require('../helpers/keys'); diff --git a/helpers/axiosClient.js b/helpers/axiosClient.js new file mode 100644 index 0000000..d41cf48 --- /dev/null +++ b/helpers/axiosClient.js @@ -0,0 +1,4 @@ +const axios = require('axios'); + +const axiosClient = axios.create() +module.exports = axiosClient diff --git a/helpers/healthCheck.js b/helpers/healthCheck.js index 71b57e7..6272f4b 100644 --- a/helpers/healthCheck.js +++ b/helpers/healthCheck.js @@ -1,4 +1,4 @@ -const axios = require('axios'); +const axios = require('../helpers/axiosClient'); const socket = require('./wsClient'); const logger = require('./logger'); const validator = require('./validator'); @@ -148,7 +148,7 @@ async function checkNodes(forceChangeActiveNode) { this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length-1)].node // Use random node from which are synced else this.activeNode = biggestGroup[0].node; // Use node with minimum ping among which are synced - + } socket.reviseConnection(this.liveNodes); let unavailableCount = this.nodesList.length - this.liveNodes.length; @@ -159,7 +159,7 @@ async function checkNodes(forceChangeActiveNode) { if (outOfSyncCount) nodesInfoString += `, ${outOfSyncCount} nodes are not synced` logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); - + } } catch (e) { From d518ae69d439693365c2fb552c15a874140c42a0 Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 25 Mar 2022 18:57:04 +0600 Subject: [PATCH 07/31] refactor: move the code to src directory --- package.json | 6 +++--- {groups => src/groups}/btc.js | 0 {groups => src/groups}/coinNetworks.js | 0 {groups => src/groups}/dash.js | 0 {groups => src/groups}/decodeMsg.js | 0 {groups => src/groups}/doge.js | 0 {groups => src/groups}/eth.js | 0 {groups => src/groups}/get.js | 0 {groups => src/groups}/getPublicKey.js | 0 {groups => src/groups}/lsk.js | 0 {groups => src/groups}/newDelegate.js | 0 {groups => src/groups}/sendMessage.js | 0 {groups => src/groups}/sendTokens.js | 0 {groups => src/groups}/voteForDelegate.js | 0 {helpers => src/helpers}/axiosClient.js | 0 {helpers => src/helpers}/bignumber.js | 0 {helpers => src/helpers}/constants.js | 0 {helpers => src/helpers}/encryptor.js | 0 {helpers => src/helpers}/healthCheck.js | 0 {helpers => src/helpers}/keys.js | 0 {helpers => src/helpers}/logger.js | 0 {helpers => src/helpers}/time.js | 0 {helpers => src/helpers}/transactionFormer.js | 0 {helpers => src/helpers}/validator.js | 0 {helpers => src/helpers}/wsClient.js | 0 index.js => src/index.js | 0 26 files changed, 3 insertions(+), 3 deletions(-) rename {groups => src/groups}/btc.js (100%) rename {groups => src/groups}/coinNetworks.js (100%) rename {groups => src/groups}/dash.js (100%) rename {groups => src/groups}/decodeMsg.js (100%) rename {groups => src/groups}/doge.js (100%) rename {groups => src/groups}/eth.js (100%) rename {groups => src/groups}/get.js (100%) rename {groups => src/groups}/getPublicKey.js (100%) rename {groups => src/groups}/lsk.js (100%) rename {groups => src/groups}/newDelegate.js (100%) rename {groups => src/groups}/sendMessage.js (100%) rename {groups => src/groups}/sendTokens.js (100%) rename {groups => src/groups}/voteForDelegate.js (100%) rename {helpers => src/helpers}/axiosClient.js (100%) rename {helpers => src/helpers}/bignumber.js (100%) rename {helpers => src/helpers}/constants.js (100%) rename {helpers => src/helpers}/encryptor.js (100%) rename {helpers => src/helpers}/healthCheck.js (100%) rename {helpers => src/helpers}/keys.js (100%) rename {helpers => src/helpers}/logger.js (100%) rename {helpers => src/helpers}/time.js (100%) rename {helpers => src/helpers}/transactionFormer.js (100%) rename {helpers => src/helpers}/validator.js (100%) rename {helpers => src/helpers}/wsClient.js (100%) rename index.js => src/index.js (100%) diff --git a/package.json b/package.json index 9511d56..02501b2 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "adamant-api", "version": "1.5.0", "description": "REST API for ADAMANT Blockchain", - "main": "index.js", + "main": "src/index.js", "scripts": { "test": "jest", - "lint": "eslint .eslintrc.js", - "lint:fix": "eslint --fix .eslintrc.js", + "lint": "eslint src", + "lint:fix": "eslint --fix src", "prepare": "husky install" }, "author": "ADAMANT Foundation (https://adamant.im)", diff --git a/groups/btc.js b/src/groups/btc.js similarity index 100% rename from groups/btc.js rename to src/groups/btc.js diff --git a/groups/coinNetworks.js b/src/groups/coinNetworks.js similarity index 100% rename from groups/coinNetworks.js rename to src/groups/coinNetworks.js diff --git a/groups/dash.js b/src/groups/dash.js similarity index 100% rename from groups/dash.js rename to src/groups/dash.js diff --git a/groups/decodeMsg.js b/src/groups/decodeMsg.js similarity index 100% rename from groups/decodeMsg.js rename to src/groups/decodeMsg.js diff --git a/groups/doge.js b/src/groups/doge.js similarity index 100% rename from groups/doge.js rename to src/groups/doge.js diff --git a/groups/eth.js b/src/groups/eth.js similarity index 100% rename from groups/eth.js rename to src/groups/eth.js diff --git a/groups/get.js b/src/groups/get.js similarity index 100% rename from groups/get.js rename to src/groups/get.js diff --git a/groups/getPublicKey.js b/src/groups/getPublicKey.js similarity index 100% rename from groups/getPublicKey.js rename to src/groups/getPublicKey.js diff --git a/groups/lsk.js b/src/groups/lsk.js similarity index 100% rename from groups/lsk.js rename to src/groups/lsk.js diff --git a/groups/newDelegate.js b/src/groups/newDelegate.js similarity index 100% rename from groups/newDelegate.js rename to src/groups/newDelegate.js diff --git a/groups/sendMessage.js b/src/groups/sendMessage.js similarity index 100% rename from groups/sendMessage.js rename to src/groups/sendMessage.js diff --git a/groups/sendTokens.js b/src/groups/sendTokens.js similarity index 100% rename from groups/sendTokens.js rename to src/groups/sendTokens.js diff --git a/groups/voteForDelegate.js b/src/groups/voteForDelegate.js similarity index 100% rename from groups/voteForDelegate.js rename to src/groups/voteForDelegate.js diff --git a/helpers/axiosClient.js b/src/helpers/axiosClient.js similarity index 100% rename from helpers/axiosClient.js rename to src/helpers/axiosClient.js diff --git a/helpers/bignumber.js b/src/helpers/bignumber.js similarity index 100% rename from helpers/bignumber.js rename to src/helpers/bignumber.js diff --git a/helpers/constants.js b/src/helpers/constants.js similarity index 100% rename from helpers/constants.js rename to src/helpers/constants.js diff --git a/helpers/encryptor.js b/src/helpers/encryptor.js similarity index 100% rename from helpers/encryptor.js rename to src/helpers/encryptor.js diff --git a/helpers/healthCheck.js b/src/helpers/healthCheck.js similarity index 100% rename from helpers/healthCheck.js rename to src/helpers/healthCheck.js diff --git a/helpers/keys.js b/src/helpers/keys.js similarity index 100% rename from helpers/keys.js rename to src/helpers/keys.js diff --git a/helpers/logger.js b/src/helpers/logger.js similarity index 100% rename from helpers/logger.js rename to src/helpers/logger.js diff --git a/helpers/time.js b/src/helpers/time.js similarity index 100% rename from helpers/time.js rename to src/helpers/time.js diff --git a/helpers/transactionFormer.js b/src/helpers/transactionFormer.js similarity index 100% rename from helpers/transactionFormer.js rename to src/helpers/transactionFormer.js diff --git a/helpers/validator.js b/src/helpers/validator.js similarity index 100% rename from helpers/validator.js rename to src/helpers/validator.js diff --git a/helpers/wsClient.js b/src/helpers/wsClient.js similarity index 100% rename from helpers/wsClient.js rename to src/helpers/wsClient.js diff --git a/index.js b/src/index.js similarity index 100% rename from index.js rename to src/index.js From bdf5f0b364029a7b71c36806028cd2ccc7a53434 Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 25 Mar 2022 19:00:04 +0600 Subject: [PATCH 08/31] refactor: convert tabs to spaces --- src/groups/decodeMsg.js | 118 +++---- src/groups/eth.js | 14 +- src/groups/get.js | 44 +-- src/groups/newDelegate.js | 10 +- src/groups/sendMessage.js | 8 +- src/groups/sendTokens.js | 26 +- src/groups/voteForDelegate.js | 8 +- src/helpers/bignumber.js | 202 ++++++------ src/helpers/constants.js | 78 ++--- src/helpers/encryptor.js | 52 +-- src/helpers/healthCheck.js | 344 ++++++++++---------- src/helpers/keys.js | 52 +-- src/helpers/logger.js | 58 ++-- src/helpers/time.js | 22 +- src/helpers/transactionFormer.js | 522 +++++++++++++++---------------- src/helpers/validator.js | 16 +- src/helpers/wsClient.js | 162 +++++----- src/index.js | 44 +-- 18 files changed, 890 insertions(+), 890 deletions(-) diff --git a/src/groups/decodeMsg.js b/src/groups/decodeMsg.js index 31829bb..797ffaf 100644 --- a/src/groups/decodeMsg.js +++ b/src/groups/decodeMsg.js @@ -4,80 +4,80 @@ const keys = require('../helpers/keys'); module.exports = (msg, senderPublicKey, passPhrase, nonce) => { - const keypair = keys.createKeypairFromPassPhrase(passPhrase); - let privateKey = keypair.privateKey; - if (typeof msg === 'string') { - msg = hexToBytes(msg) - } - if (typeof nonce === 'string') { - nonce = hexToBytes(nonce) - } + const keypair = keys.createKeypairFromPassPhrase(passPhrase); + let privateKey = keypair.privateKey; + if (typeof msg === 'string') { + msg = hexToBytes(msg) + } + if (typeof nonce === 'string') { + nonce = hexToBytes(nonce) + } - if (typeof senderPublicKey === 'string') { - senderPublicKey = hexToBytes(senderPublicKey) - } + if (typeof senderPublicKey === 'string') { + senderPublicKey = hexToBytes(senderPublicKey) + } - if (typeof privateKey === 'string') { - privateKey = hexToBytes(privateKey) - } - const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey); - const DHSecretKey = ed2curve.convertSecretKey(privateKey); - const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey); - return decrypted ? Utf8ArrayToStr(decrypted) : '' + if (typeof privateKey === 'string') { + privateKey = hexToBytes(privateKey) + } + const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey); + const DHSecretKey = ed2curve.convertSecretKey(privateKey); + const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey); + return decrypted ? Utf8ArrayToStr(decrypted) : '' } function hexToBytes(hexString = '') { - const bytes = [] + const bytes = [] - for (let c = 0; c < hexString.length; c += 2) { - bytes.push(parseInt(hexString.substr(c, 2), 16)) - } + for (let c = 0; c < hexString.length; c += 2) { + bytes.push(parseInt(hexString.substr(c, 2), 16)) + } - return Uint8Array.from(bytes); + return Uint8Array.from(bytes); } function Utf8ArrayToStr(array) { - var out, i, len, c; - var char2, char3; + var out, i, len, c; + var char2, char3; - out = ""; - len = array.length; - i = 0; - while (i < len) { - c = array[i++]; - switch (c >> 4) { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - case 6: - case 7: - // 0xxxxxxx - out += String.fromCharCode(c); - break; - case 12: - case 13: - // 110x xxxx 10xx xxxx - char2 = array[i++]; - out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); - break; - case 14: - // 1110 xxxx 10xx xxxx 10xx xxxx - char2 = array[i++]; - char3 = array[i++]; - out += String.fromCharCode(((c & 0x0F) << 12) | - ((char2 & 0x3F) << 6) | - ((char3 & 0x3F) << 0)); - break; - } - } + out = ""; + len = array.length; + i = 0; + while (i < len) { + c = array[i++]; + switch (c >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + out += String.fromCharCode(c); + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + char2 = array[i++]; + out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + char2 = array[i++]; + char3 = array[i++]; + out += String.fromCharCode(((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | + ((char3 & 0x3F) << 0)); + break; + } + } - return out; + return out; } \ No newline at end of file diff --git a/src/groups/eth.js b/src/groups/eth.js index a8b086f..62e4a6e 100644 --- a/src/groups/eth.js +++ b/src/groups/eth.js @@ -11,14 +11,14 @@ const eth = { } */ eth.keys = passphrase => { - const mnemonic = new Mnemonic(passphrase, Mnemonic.Words.ENGLISH); - const seed = mnemonic.toSeed(); - const privateKey = hdkey.fromMasterSeed(seed).derive(HD_KEY_PATH)._privateKey; + const mnemonic = new Mnemonic(passphrase, Mnemonic.Words.ENGLISH); + const seed = mnemonic.toSeed(); + const privateKey = hdkey.fromMasterSeed(seed).derive(HD_KEY_PATH)._privateKey; - return { - address: bufferToHex(privateToAddress(privateKey)), - privateKey: bufferToHex(privateKey) - }; + return { + address: bufferToHex(privateToAddress(privateKey)), + privateKey: bufferToHex(privateKey) + }; }; module.exports = eth; diff --git a/src/groups/get.js b/src/groups/get.js index 83f8e81..ead691e 100644 --- a/src/groups/get.js +++ b/src/groups/get.js @@ -5,11 +5,11 @@ const validator = require('../helpers/validator'); const DEFAULT_GET_REQUEST_RETRIES = 3; // How much re-tries for get-requests by default. Total 3+1 tries module.exports = (nodeManager) => { - return (endpoint, params, maxRetries = DEFAULT_GET_REQUEST_RETRIES, retryNo = 0) => { + return (endpoint, params, maxRetries = DEFAULT_GET_REQUEST_RETRIES, retryNo = 0) => { - let url = trimAny(endpoint, "/ ").replace(/^api\//, ''); - if (!url || !validator.validateEndpoint(endpoint)) - return validator.badParameter('endpoint') + let url = trimAny(endpoint, "/ ").replace(/^api\//, ''); + if (!url || !validator.validateEndpoint(endpoint)) + return validator.badParameter('endpoint') url = nodeManager.node() + '/api/' + url; return axios.get(url, { params }) @@ -17,15 +17,15 @@ module.exports = (nodeManager) => { return validator.formatRequestResults(response, true) }) .catch(function (error) { - let logMessage = `[ADAMANT js-api] Get-request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo+1} of ${maxRetries+1}.`; - if (retryNo < maxRetries) { - logger.log(`${logMessage} Retrying…`); - return nodeManager.changeNodes() - .then(function () { - return module.exports(nodeManager)(endpoint, params, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); + let logMessage = `[ADAMANT js-api] Get-request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .then(function () { + return module.exports(nodeManager)(endpoint, params, maxRetries, ++retryNo) + }) + } + logger.warn(`${logMessage} No more attempts, returning error.`); return validator.formatRequestResults(error, false) }) @@ -33,14 +33,14 @@ module.exports = (nodeManager) => { }; function trimAny(str, chars) { - if (!str || typeof str !== 'string') - return '' - let start = 0, - end = str.length; - while(start < end && chars.indexOf(str[start]) >= 0) - ++start; - while(end > start && chars.indexOf(str[end - 1]) >= 0) - --end; - return (start > 0 || end < str.length) ? str.substring(start, end) : str; + if (!str || typeof str !== 'string') + return '' + let start = 0, + end = str.length; + while (start < end && chars.indexOf(str[start]) >= 0) + ++start; + while (end > start && chars.indexOf(str[end - 1]) >= 0) + --end; + return (start > 0 || end < str.length) ? str.substring(start, end) : str; } diff --git a/src/groups/newDelegate.js b/src/groups/newDelegate.js index b3ddb73..9d94d7b 100644 --- a/src/groups/newDelegate.js +++ b/src/groups/newDelegate.js @@ -9,27 +9,27 @@ const DEFAULT_NEW_DELEGATE_RETRIES = 4; // How much re-tries for send tokens req module.exports = (nodeManager) => { /** - * Registers user account as delegate + * Registers user account as delegate * @param {string} passPhrase Senders's passPhrase. Sender's address will be derived from it. * @param {string} username Delegate name you want to register with. * It must be unique in ADAMANT blockchain. It should not be similar to ADAMANT address. * Delegate name can only contain alphanumeric characters and symbols !@$&_. * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results - */ - return async (passPhrase, username, maxRetries = DEFAULT_NEW_DELEGATE_RETRIES, retryNo = 0) => { + */ + return async (passPhrase, username, maxRetries = DEFAULT_NEW_DELEGATE_RETRIES, retryNo = 0) => { let transaction; try { if (!validator.validatePassPhrase(passPhrase)) { - return validator.badParameter('passPhrase'); + return validator.badParameter('passPhrase'); } const keyPair = keys.createKeypairFromPassPhrase(passPhrase); if (!validator.validateDelegateName(username)) { - return validator.badParameter('username'); + return validator.badParameter('username'); } const type = constants.transactionTypes.DELEGATE; diff --git a/src/groups/sendMessage.js b/src/groups/sendMessage.js index d230e65..156fd5f 100644 --- a/src/groups/sendMessage.js +++ b/src/groups/sendMessage.js @@ -11,7 +11,7 @@ const DEFAULT_SEND_MESSAGE_RETRIES = 4; // How much re-tries for send message re module.exports = (nodeManager) => { /** - * Encrypts a message, creates Message transaction, signs it, and broadcasts to ADAMANT network. Supports Basic, Rich and Signal Message Types. + * Encrypts a message, creates Message transaction, signs it, and broadcasts to ADAMANT network. Supports Basic, Rich and Signal Message Types. * See https://github.com/Adamant-im/adamant/wiki/Message-Types * @param {string} passPhrase Senders's passPhrase. Sender's address will be derived from it. * @param {string} addressOrPublicKey Recipient's ADAMANT address or public key. @@ -24,8 +24,8 @@ module.exports = (nodeManager) => { * @param {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results - */ - return async (passPhrase, addressOrPublicKey, message, message_type = 'basic', amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_MESSAGE_RETRIES, retryNo = 0) => { + */ + return async (passPhrase, addressOrPublicKey, message, message_type = 'basic', amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_MESSAGE_RETRIES, retryNo = 0) => { let keyPair, data; let address, publicKey; @@ -33,7 +33,7 @@ module.exports = (nodeManager) => { try { if (!validator.validatePassPhrase(passPhrase)) - return validator.badParameter('passPhrase') + return validator.badParameter('passPhrase') keyPair = keys.createKeypairFromPassPhrase(passPhrase); diff --git a/src/groups/sendTokens.js b/src/groups/sendTokens.js index 8a14a0d..24da993 100644 --- a/src/groups/sendTokens.js +++ b/src/groups/sendTokens.js @@ -9,7 +9,7 @@ const DEFAULT_SEND_TOKENS_RETRIES = 4; // How much re-tries for send tokens requ module.exports = (nodeManager) => { /** - * Creates Token Transfer transaction, signs it, and broadcasts to ADAMANT network + * Creates Token Transfer transaction, signs it, and broadcasts to ADAMANT network * See https://github.com/Adamant-im/adamant/wiki/Transaction-Types#type-0-token-transfer-transaction * @param {string} passPhrase Senders's passPhrase. Sender's address will be derived from it. * @param {string} addressOrPublicKey Recipient's ADAMANT address or public key. @@ -18,8 +18,8 @@ module.exports = (nodeManager) => { * @param {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results - */ - return (passPhrase, addressOrPublicKey, amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { + */ + return (passPhrase, addressOrPublicKey, amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { let transaction; let address, publicKey; @@ -27,7 +27,7 @@ module.exports = (nodeManager) => { try { if (!validator.validatePassPhrase(passPhrase)) - return validator.badParameter('passPhrase') + return validator.badParameter('passPhrase') const keyPair = keys.createKeypairFromPassPhrase(passPhrase); @@ -76,15 +76,15 @@ module.exports = (nodeManager) => { return validator.formatRequestResults(response, true) }) .catch(function (error) { - let logMessage = `[ADAMANT js-api] Send tokens request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo+1} of ${maxRetries+1}.`; - if (retryNo < maxRetries) { - logger.log(`${logMessage} Retrying…`); - return nodeManager.changeNodes() - .then(function () { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); + let logMessage = `[ADAMANT js-api] Send tokens request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .then(function () { + return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) + }) + } + logger.warn(`${logMessage} No more attempts, returning error.`); return validator.formatRequestResults(error, false) }) diff --git a/src/groups/voteForDelegate.js b/src/groups/voteForDelegate.js index 0fae4dc..3393a96 100644 --- a/src/groups/voteForDelegate.js +++ b/src/groups/voteForDelegate.js @@ -12,21 +12,21 @@ const publicKeysCache = { }; module.exports = (nodeManager) => { /** - * Creates votes for delegate transaction, signs it, and broadcasts to ADAMANT network + * Creates votes for delegate transaction, signs it, and broadcasts to ADAMANT network * See https://github.com/Adamant-im/adamant/wiki/Transaction-Types#type-3-vote-for-delegate-transaction * @param {string} passPhrase Senders's passPhrase. Sender's address will be derived from it. * @param {string[]} votes PublicKeys, ADM addresses and delegate names for upvote and downvote. * It would be more efficient to pass publicKey, otherwise the api will make additional queries * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results - */ - return async (passPhrase, votes, maxRetries = DEFAULT_VOTE_FOR_DELEGATE_RETRIES, retryNo = 0) => { + */ + return async (passPhrase, votes, maxRetries = DEFAULT_VOTE_FOR_DELEGATE_RETRIES, retryNo = 0) => { let transaction; try { if (!validator.validatePassPhrase(passPhrase)) { - return validator.badParameter('passPhrase'); + return validator.badParameter('passPhrase'); } const keyPair = keys.createKeypairFromPassPhrase(passPhrase); diff --git a/src/helpers/bignumber.js b/src/helpers/bignumber.js index c6b556d..1cce693 100644 --- a/src/helpers/bignumber.js +++ b/src/helpers/bignumber.js @@ -2,118 +2,118 @@ 'use strict' /** - * Buffer functions that implements bignumber. - * @memberof module:helpers - * @requires bignumber - * @constructor + * Buffer functions that implements bignumber. + * @memberof module:helpers + * @requires bignumber + * @constructor */ var BigNumber = require('bignumber.js') /** - * Creates an instance from a Buffer. - * @param {ArrayBuffer} buf - * @param {Object} opts - * @return {ArrayBuffer} new BigNumber instance - * @throws {RangeError} error description multiple of size + * Creates an instance from a Buffer. + * @param {ArrayBuffer} buf + * @param {Object} opts + * @return {ArrayBuffer} new BigNumber instance + * @throws {RangeError} error description multiple of size */ BigNumber.fromBuffer = function (buf, opts) { - if (!opts) opts = {} - - var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' - - var size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1) - - if (buf.length % size !== 0) { - throw new RangeError('Buffer length (' + buf.length + ')' + - ' must be a multiple of size (' + size + ')' - ) - } - - var hex = [] - for (var i = 0; i < buf.length; i += size) { - var chunk = [] - for (var j = 0; j < size; j++) { - chunk.push(buf[i + (endian === 'big' ? j : (size - j - 1))]) - } - - hex.push(chunk - .map(function (c) { - return (c < 16 ? '0' : '') + c.toString(16) - }) - .join('') - ) - } - - return new BigNumber(hex.join(''), 16) + if (!opts) opts = {} + + var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' + + var size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1) + + if (buf.length % size !== 0) { + throw new RangeError('Buffer length (' + buf.length + ')' + + ' must be a multiple of size (' + size + ')' + ) + } + + var hex = [] + for (var i = 0; i < buf.length; i += size) { + var chunk = [] + for (var j = 0; j < size; j++) { + chunk.push(buf[i + (endian === 'big' ? j : (size - j - 1))]) + } + + hex.push(chunk + .map(function (c) { + return (c < 16 ? '0' : '') + c.toString(16) + }) + .join('') + ) + } + + return new BigNumber(hex.join(''), 16) } /** - * Returns an instance as Buffer. - * @param {Object} opts - * @return {ArrayBuffer} new buffer | error message invalid option + * Returns an instance as Buffer. + * @param {Object} opts + * @return {ArrayBuffer} new buffer | error message invalid option */ BigNumber.prototype.toBuffer = function (opts) { - if (typeof opts === 'string') { - if (opts !== 'mpint') return 'Unsupported Buffer representation' - - var abs = this.abs() - var buf = abs.toBuffer({ size: 1, endian: 'big' }) - var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length - if (buf[0] & 0x80) len++ - - var ret = Buffer.alloc(4 + len) - if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0)) - if (buf[0] & 0x80) ret[4] = 0 - - ret[0] = len & (0xff << 24) - ret[1] = len & (0xff << 16) - ret[2] = len & (0xff << 8) - ret[3] = len & (0xff << 0) - - // Two's compliment for negative integers - var isNeg = this.lt(0) - if (isNeg) { - for (var i = 4; i < ret.length; i++) { - ret[i] = 0xff - ret[i] - } - } - ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0) - if (isNeg) ret[ret.length - 1] ++ - - return ret - } - - if (!opts) opts = {} - - var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' - - var hex = this.toString(16) - if (hex.charAt(0) === '-') { - throw new Error( - 'Converting negative numbers to Buffers not supported yet' - ) - } - - var size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1) - - var len = Math.ceil(hex.length / (2 * size)) * size - var buf = Buffer.alloc(len) - - // Zero-pad the hex string so the chunks are all `size` long - while (hex.length < 2 * len) hex = '0' + hex - - var hx = hex - .split(new RegExp('(.{' + (2 * size) + '})')) - .filter(function (s) { return s.length > 0 }) - - hx.forEach(function (chunk, i) { - for (var j = 0; j < size; j++) { - var ix = i * size + (endian === 'big' ? j : size - j - 1) - buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16) - } - }) - - return buf + if (typeof opts === 'string') { + if (opts !== 'mpint') return 'Unsupported Buffer representation' + + var abs = this.abs() + var buf = abs.toBuffer({ size: 1, endian: 'big' }) + var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length + if (buf[0] & 0x80) len++ + + var ret = Buffer.alloc(4 + len) + if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0)) + if (buf[0] & 0x80) ret[4] = 0 + + ret[0] = len & (0xff << 24) + ret[1] = len & (0xff << 16) + ret[2] = len & (0xff << 8) + ret[3] = len & (0xff << 0) + + // Two's compliment for negative integers + var isNeg = this.lt(0) + if (isNeg) { + for (var i = 4; i < ret.length; i++) { + ret[i] = 0xff - ret[i] + } + } + ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0) + if (isNeg) ret[ret.length - 1]++ + + return ret + } + + if (!opts) opts = {} + + var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' + + var hex = this.toString(16) + if (hex.charAt(0) === '-') { + throw new Error( + 'Converting negative numbers to Buffers not supported yet' + ) + } + + var size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1) + + var len = Math.ceil(hex.length / (2 * size)) * size + var buf = Buffer.alloc(len) + + // Zero-pad the hex string so the chunks are all `size` long + while (hex.length < 2 * len) hex = '0' + hex + + var hx = hex + .split(new RegExp('(.{' + (2 * size) + '})')) + .filter(function (s) { return s.length > 0 }) + + hx.forEach(function (chunk, i) { + for (var j = 0; j < size; j++) { + var ix = i * size + (endian === 'big' ? j : size - j - 1) + buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16) + } + }) + + return buf } module.exports = BigNumber diff --git a/src/helpers/constants.js b/src/helpers/constants.js index bee5c31..28161ce 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -1,43 +1,43 @@ module.exports = { - epochTime: new Date(Date.UTC(2017, 8, 2, 17, 0, 0, 0)), - fees: { - send: 50000000, - vote: 1000000000, - secondsignature: 500000000, - delegate: 30000000000, - multisignature: 500000000, - dapp: 2500000000, - old_chat_message: 500000, - chat_message: 100000, - profile_update: 5000000, - avatar_upload: 10000000, - state_store: 100000 - }, - transactionTypes: { - SEND: 0, - SIGNATURE: 1, - DELEGATE: 2, - VOTE: 3, - MULTI: 4, - DAPP: 5, - IN_TRANSFER: 6, - OUT_TRANSFER: 7, - CHAT_MESSAGE: 8, - STATE: 9 - }, - maxVotesPerTransaction: 33, - SAT: 100000000, - RE_HEX: /^[a-fA-F0-9]+$/, - RE_BASE64: /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/, - RE_ADM_ADDRESS: /^U([0-9]{6,})$/, - RE_ADM_VOTE_FOR_PUBLIC_KEY: /^(\+|-)[a-fA-F0-9]{64}$/, - RE_ADM_VOTE_FOR_ADDRESS: /^(\+|-)U([0-9]{6,})$/, - RE_ADM_VOTE_FOR_DELEGATE_NAME: /^(\+|-)([a-z0-9!@$&_]{1,20})$/, - RE_ADM_DELEGATE_NAME: /^[a-z0-9!@$&_]{1,20}$/, - RE_BTC_ADDRESS: /^(bc1|[13])[a-km-zA-HJ-NP-Z02-9]{25,39}$/, - RE_DASH_ADDRESS: /^[7X][1-9A-HJ-NP-Za-km-z]{33,}$/, - RE_DOGE_ADDRESS: /^[A|D|9][A-Z0-9]([0-9a-zA-Z]{9,})$/, - RE_LSK_ADDRESS: /^[0-9]{2,21}L$/ + epochTime: new Date(Date.UTC(2017, 8, 2, 17, 0, 0, 0)), + fees: { + send: 50000000, + vote: 1000000000, + secondsignature: 500000000, + delegate: 30000000000, + multisignature: 500000000, + dapp: 2500000000, + old_chat_message: 500000, + chat_message: 100000, + profile_update: 5000000, + avatar_upload: 10000000, + state_store: 100000 + }, + transactionTypes: { + SEND: 0, + SIGNATURE: 1, + DELEGATE: 2, + VOTE: 3, + MULTI: 4, + DAPP: 5, + IN_TRANSFER: 6, + OUT_TRANSFER: 7, + CHAT_MESSAGE: 8, + STATE: 9 + }, + maxVotesPerTransaction: 33, + SAT: 100000000, + RE_HEX: /^[a-fA-F0-9]+$/, + RE_BASE64: /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/, + RE_ADM_ADDRESS: /^U([0-9]{6,})$/, + RE_ADM_VOTE_FOR_PUBLIC_KEY: /^(\+|-)[a-fA-F0-9]{64}$/, + RE_ADM_VOTE_FOR_ADDRESS: /^(\+|-)U([0-9]{6,})$/, + RE_ADM_VOTE_FOR_DELEGATE_NAME: /^(\+|-)([a-z0-9!@$&_]{1,20})$/, + RE_ADM_DELEGATE_NAME: /^[a-z0-9!@$&_]{1,20}$/, + RE_BTC_ADDRESS: /^(bc1|[13])[a-km-zA-HJ-NP-Z02-9]{25,39}$/, + RE_DASH_ADDRESS: /^[7X][1-9A-HJ-NP-Za-km-z]{33,}$/, + RE_DOGE_ADDRESS: /^[A|D|9][A-Z0-9]([0-9a-zA-Z]{9,})$/, + RE_LSK_ADDRESS: /^[0-9]{2,21}L$/ } diff --git a/src/helpers/encryptor.js b/src/helpers/encryptor.js index fe5e4cf..e51e191 100644 --- a/src/helpers/encryptor.js +++ b/src/helpers/encryptor.js @@ -4,32 +4,32 @@ var ed2curve = require('ed2curve') module.exports = { - bytesToHex: function (bytes) { - for (var hex = [], i = 0; i < bytes.length; i++) { - hex.push((bytes[i] >>> 4).toString(16)) - hex.push((bytes[i] & 0xF).toString(16)) - } - return hex.join('') - }, + bytesToHex: function (bytes) { + for (var hex = [], i = 0; i < bytes.length; i++) { + hex.push((bytes[i] >>> 4).toString(16)) + hex.push((bytes[i] & 0xF).toString(16)) + } + return hex.join('') + }, - hexToBytes: function (hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) { - bytes.push(parseInt(hex.substr(c, 2), 16)) - } - return bytes - }, - - encodeMessage: function (msg, keypair, recipientPublicKey) { - var nonce = Buffer.allocUnsafe(24) - sodium.randombytes(nonce) - var plainText = Buffer.from(msg.toString()) - var DHPublicKey = ed2curve.convertPublicKey(new Uint8Array(this.hexToBytes(recipientPublicKey))) - var DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey) - var encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey) - return { - message: this.bytesToHex(encrypted), - own_message: this.bytesToHex(nonce) - } - } + hexToBytes: function (hex) { + for (var bytes = [], c = 0; c < hex.length; c += 2) { + bytes.push(parseInt(hex.substr(c, 2), 16)) + } + return bytes + }, + + encodeMessage: function (msg, keypair, recipientPublicKey) { + var nonce = Buffer.allocUnsafe(24) + sodium.randombytes(nonce) + var plainText = Buffer.from(msg.toString()) + var DHPublicKey = ed2curve.convertPublicKey(new Uint8Array(this.hexToBytes(recipientPublicKey))) + var DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey) + var encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey) + return { + message: this.bytesToHex(encrypted), + own_message: this.bytesToHex(nonce) + } + } } diff --git a/src/helpers/healthCheck.js b/src/helpers/healthCheck.js index df80d48..2581650 100644 --- a/src/helpers/healthCheck.js +++ b/src/helpers/healthCheck.js @@ -9,193 +9,193 @@ const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced module.exports = (nodes, checkHealthAtStartup = true) => { - isCheckingNodes = false; - nodesList = nodes; - activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data - liveNodes = []; - - /** - * Updates active nodes. If nodes are already updating, returns Promise of previous call - * @returns {Promise} Call changeNodes().then to do something when update complete - */ - function changeNodes(isPlannedUpdate = false) { - if (!isCheckingNodes) { - changeNodesPromise = new Promise(async (resolve) => { - if (!isPlannedUpdate) { - logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); - } - await checkNodes(isPlannedUpdate ? false : true) - resolve(true) - }); - } - return changeNodesPromise - } - - - if (checkHealthAtStartup) { - changeNodes(true) - setInterval(() => { changeNodes(true) }, CHECK_NODES_INTERVAL); - } - - return { - - /** - * @returns {string} Current active node, f. e. http://88.198.156.44:36666 - */ - node: () => { - return activeNode; - }, - - changeNodes - - }; + isCheckingNodes = false; + nodesList = nodes; + activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data + liveNodes = []; + + /** + * Updates active nodes. If nodes are already updating, returns Promise of previous call + * @returns {Promise} Call changeNodes().then to do something when update complete + */ + function changeNodes(isPlannedUpdate = false) { + if (!isCheckingNodes) { + changeNodesPromise = new Promise(async (resolve) => { + if (!isPlannedUpdate) { + logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); + } + await checkNodes(isPlannedUpdate ? false : true) + resolve(true) + }); + } + return changeNodesPromise + } + + + if (checkHealthAtStartup) { + changeNodes(true) + setInterval(() => { changeNodes(true) }, CHECK_NODES_INTERVAL); + } + + return { + + /** + * @returns {string} Current active node, f. e. http://88.198.156.44:36666 + */ + node: () => { + return activeNode; + }, + + changeNodes + + }; }; /** - * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active - */ + * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active + */ async function checkNodes(forceChangeActiveNode) { - this.isCheckingNodes = true; - this.liveNodes = []; - - try { - - for (const n of this.nodesList) { - try { - let start = unixTimestamp(); - let req = await checkNode(n + '/api/node/status'); - let url = n.replace(/^https?:\/\/(.*)$/, '$1').split(":")[0]; - let ifIP = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(url) - - if (req.status) { - this.liveNodes.push({ - node: n, - ifHttps: n.startsWith("https"), - ifIP, - url, - outOfSync: false, - ping: unixTimestamp() - start, - height: req.status.network.height, - heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), - ip: ifIP ? url : await getIP(url), - socketSupport: req.status.wsClient && req.status.wsClient.enabled, - wsPort: req.status.wsClient.port - }); - - } else { - logger.log(`[ADAMANT js-api] Health check: Node ${n} haven't returned its status`); - } - - } catch (e) { - logger.log(`[ADAMANT js-api] Health check: Error while checking node ${n}, ` + e); - } - } - - const count = this.liveNodes.length; - let outOfSyncCount = 0; - - if (!count) { - logger.error(`[ADAMANT js-api] Health check: All of ${this.nodesList.length} nodes are unavailable. Check internet connection and nodes list in config.`); - } else { - - // Set activeNode to one that have maximum height and minimum ping - if (count === 1) { - - this.activeNode = this.liveNodes[0].node; - - } else if (count === 2) { - - const h0 = this.liveNodes[0]; - const h1 = this.liveNodes[1]; - this.activeNode = h0.height > h1.height ? h0.node : h1.node; - // Mark node outOfSync if needed - if (h0.heightEpsilon > h1.heightEpsilon) { - this.liveNodes[1].outOfSync = true - outOfSyncCount += 1; - } else if (h0.heightEpsilon < h1.heightEpsilon) { - this.liveNodes[0].outOfSync = true - outOfSyncCount += 1; - } - - } else { - - let biggestGroup = []; - // Removing lodash: const groups = _.groupBy(this.liveNodes, n => n.heightEpsilon); - const groups = this.liveNodes.reduce(function (grouped, node) { - var int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded - if (!grouped.hasOwnProperty(int)) { - grouped[int] = []; - } - grouped[int].push(node); - return grouped; - }, {}); - - Object.keys(groups).forEach(key => { - if (groups[key].length > biggestGroup.length) { - biggestGroup = groups[key]; - } - }); - - // All the nodes from the biggestGroup list are considered to be in sync, all the others are not - this.liveNodes.forEach(node => { - node.outOfSync = !biggestGroup.includes(node) - }) - outOfSyncCount = this.liveNodes.length - biggestGroup.length; - - biggestGroup.sort((a, b) => a.ping - b.ping); - this.liveNodes.sort((a, b) => a.ping - b.ping); - - if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) - this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node // Use random node from which are synced - else - this.activeNode = biggestGroup[0].node; // Use node with minimum ping among which are synced - - } - socket.reviseConnection(this.liveNodes); - let unavailableCount = this.nodesList.length - this.liveNodes.length; - let supportedCount = this.liveNodes.length - outOfSyncCount; - let nodesInfoString = ''; - if (unavailableCount) - nodesInfoString += `, ${unavailableCount} nodes didn't respond` - if (outOfSyncCount) - nodesInfoString += `, ${outOfSyncCount} nodes are not synced` - logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); - - } - - } catch (e) { - logger.warn('[ADAMANT js-api] Health check: Error in checkNodes(), ' + e); - } - - this.isCheckingNodes = false; + this.isCheckingNodes = true; + this.liveNodes = []; + + try { + + for (const n of this.nodesList) { + try { + let start = unixTimestamp(); + let req = await checkNode(n + '/api/node/status'); + let url = n.replace(/^https?:\/\/(.*)$/, '$1').split(":")[0]; + let ifIP = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(url) + + if (req.status) { + this.liveNodes.push({ + node: n, + ifHttps: n.startsWith("https"), + ifIP, + url, + outOfSync: false, + ping: unixTimestamp() - start, + height: req.status.network.height, + heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), + ip: ifIP ? url : await getIP(url), + socketSupport: req.status.wsClient && req.status.wsClient.enabled, + wsPort: req.status.wsClient.port + }); + + } else { + logger.log(`[ADAMANT js-api] Health check: Node ${n} haven't returned its status`); + } + + } catch (e) { + logger.log(`[ADAMANT js-api] Health check: Error while checking node ${n}, ` + e); + } + } + + const count = this.liveNodes.length; + let outOfSyncCount = 0; + + if (!count) { + logger.error(`[ADAMANT js-api] Health check: All of ${this.nodesList.length} nodes are unavailable. Check internet connection and nodes list in config.`); + } else { + + // Set activeNode to one that have maximum height and minimum ping + if (count === 1) { + + this.activeNode = this.liveNodes[0].node; + + } else if (count === 2) { + + const h0 = this.liveNodes[0]; + const h1 = this.liveNodes[1]; + this.activeNode = h0.height > h1.height ? h0.node : h1.node; + // Mark node outOfSync if needed + if (h0.heightEpsilon > h1.heightEpsilon) { + this.liveNodes[1].outOfSync = true + outOfSyncCount += 1; + } else if (h0.heightEpsilon < h1.heightEpsilon) { + this.liveNodes[0].outOfSync = true + outOfSyncCount += 1; + } + + } else { + + let biggestGroup = []; + // Removing lodash: const groups = _.groupBy(this.liveNodes, n => n.heightEpsilon); + const groups = this.liveNodes.reduce(function (grouped, node) { + var int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded + if (!grouped.hasOwnProperty(int)) { + grouped[int] = []; + } + grouped[int].push(node); + return grouped; + }, {}); + + Object.keys(groups).forEach(key => { + if (groups[key].length > biggestGroup.length) { + biggestGroup = groups[key]; + } + }); + + // All the nodes from the biggestGroup list are considered to be in sync, all the others are not + this.liveNodes.forEach(node => { + node.outOfSync = !biggestGroup.includes(node) + }) + outOfSyncCount = this.liveNodes.length - biggestGroup.length; + + biggestGroup.sort((a, b) => a.ping - b.ping); + this.liveNodes.sort((a, b) => a.ping - b.ping); + + if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) + this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node // Use random node from which are synced + else + this.activeNode = biggestGroup[0].node; // Use node with minimum ping among which are synced + + } + socket.reviseConnection(this.liveNodes); + let unavailableCount = this.nodesList.length - this.liveNodes.length; + let supportedCount = this.liveNodes.length - outOfSyncCount; + let nodesInfoString = ''; + if (unavailableCount) + nodesInfoString += `, ${unavailableCount} nodes didn't respond` + if (outOfSyncCount) + nodesInfoString += `, ${outOfSyncCount} nodes are not synced` + logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); + + } + + } catch (e) { + logger.warn('[ADAMANT js-api] Health check: Error in checkNodes(), ' + e); + } + + this.isCheckingNodes = false; } async function getIP(url) { - try { - let addresses = await dnsPromises.resolve4(url); - if (addresses && addresses[0] !== '0.0.0.0') - return addresses[0] - } catch (e) { } + try { + let addresses = await dnsPromises.resolve4(url); + if (addresses && addresses[0] !== '0.0.0.0') + return addresses[0] + } catch (e) { } }; /** - * Requests status from a single ADAMANT node - * @param url {string} Node URL to request - * @returns {Promise} Node's status information - */ + * Requests status from a single ADAMANT node + * @param url {string} Node URL to request + * @returns {Promise} Node's status information + */ function checkNode(url) { - return axios.get(url) - .then(function (response) { - return { status: response.data } - }) - .catch(function (error) { - return false - }) + return axios.get(url) + .then(function (response) { + return { status: response.data } + }) + .catch(function (error) { + return false + }) }; function unixTimestamp() { - return new Date().getTime(); + return new Date().getTime(); } diff --git a/src/helpers/keys.js b/src/helpers/keys.js index 0e2b317..e184a79 100644 --- a/src/helpers/keys.js +++ b/src/helpers/keys.js @@ -5,35 +5,35 @@ var bignum = require('./bignumber.js'); module.exports = { - createNewPassPhrase: function () { - return new Mnemonic(Mnemonic.Words.ENGLISH).toString(); - }, + createNewPassPhrase: function () { + return new Mnemonic(Mnemonic.Words.ENGLISH).toString(); + }, - makeKeypairFromHash: function (hash) { - var keypair = sodium.crypto_sign_seed_keypair(hash); - return { - publicKey: keypair.publicKey, - privateKey: keypair.secretKey - }; - }, + makeKeypairFromHash: function (hash) { + var keypair = sodium.crypto_sign_seed_keypair(hash); + return { + publicKey: keypair.publicKey, + privateKey: keypair.secretKey + }; + }, - createHashFromPassPhrase: function (passPhrase) { - var secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); - return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); - }, + createHashFromPassPhrase: function (passPhrase) { + var secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); + return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); + }, - createKeypairFromPassPhrase: function (passPhrase) { - var hash = this.createHashFromPassPhrase(passPhrase); - return this.makeKeypairFromHash(hash); - }, + createKeypairFromPassPhrase: function (passPhrase) { + var hash = this.createHashFromPassPhrase(passPhrase); + return this.makeKeypairFromHash(hash); + }, - createAddressFromPublicKey: function (publicKey) { - var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); - var temp = Buffer.alloc(8); - for (var i = 0; i < 8; i++) { - temp[i] = publicKeyHash[7 - i]; - } - return 'U' + bignum.fromBuffer(temp).toString(); - } + createAddressFromPublicKey: function (publicKey) { + var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); + var temp = Buffer.alloc(8); + for (var i = 0; i < 8; i++) { + temp[i] = publicKeyHash[7 - i]; + } + return 'U' + bignum.fromBuffer(temp).toString(); + } } diff --git a/src/helpers/logger.js b/src/helpers/logger.js index 43ca552..542f884 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -1,34 +1,34 @@ let logger = { - errorLevel: 'log', - l: console, - - initLogger(errorLevel, log) { - if (errorLevel) - this.errorLevel = errorLevel; - if (log) - this.l = log; - }, - - error(str) { - if (['error', 'warn', 'info', 'log'].includes(this.errorLevel)) - this.l.error(str); - }, - - warn(str) { - if (['warn', 'info', 'log'].includes(this.errorLevel)) - this.l.warn(str); - }, - - info(str) { - if (['info', 'log'].includes(this.errorLevel)) - this.l.info(str); - }, - - log(str) { - if (['log'].includes(this.errorLevel)) - this.l.log(str); - } + errorLevel: 'log', + l: console, + + initLogger(errorLevel, log) { + if (errorLevel) + this.errorLevel = errorLevel; + if (log) + this.l = log; + }, + + error(str) { + if (['error', 'warn', 'info', 'log'].includes(this.errorLevel)) + this.l.error(str); + }, + + warn(str) { + if (['warn', 'info', 'log'].includes(this.errorLevel)) + this.l.warn(str); + }, + + info(str) { + if (['info', 'log'].includes(this.errorLevel)) + this.l.info(str); + }, + + log(str) { + if (['log'].includes(this.errorLevel)) + this.l.log(str); + } }; diff --git a/src/helpers/time.js b/src/helpers/time.js index 4e46bf4..c57860e 100644 --- a/src/helpers/time.js +++ b/src/helpers/time.js @@ -2,17 +2,17 @@ const constants = require('./constants.js'); module.exports = { - getEpochTime: function (time) { - if (time === undefined) { - time = Date.now(); - } - var d = constants.epochTime; - var t = d.getTime(); - return Math.floor((time - t) / 1000); - }, + getEpochTime: function (time) { + if (time === undefined) { + time = Date.now(); + } + var d = constants.epochTime; + var t = d.getTime(); + return Math.floor((time - t) / 1000); + }, - getTime: function (time) { - return this.getEpochTime(time); - } + getTime: function (time) { + return this.getEpochTime(time); + } } diff --git a/src/helpers/transactionFormer.js b/src/helpers/transactionFormer.js index 52107cd..aa4c224 100644 --- a/src/helpers/transactionFormer.js +++ b/src/helpers/transactionFormer.js @@ -8,266 +8,266 @@ const time = require('./time.js'); module.exports = { - createTransaction: function (type, data) { - switch (type) { - case constants.transactionTypes.SEND: - return this.createSendTransaction(data); - case constants.transactionTypes.VOTE: - return this.createVoteTransaction(data); - case constants.transactionTypes.DELEGATE: - return this.createDelegateTransaction(data); - case constants.transactionTypes.CHAT_MESSAGE: - return this.createChatTransaction(data); - case constants.transactionTypes.STATE: - return this.createStateTransaction(data); - } - return {}; - }, - - createBasicTransaction: function (data) { - var transaction = { type: data.transactionType, amount: 0, timestamp: time.getTime(), asset: {}, senderPublicKey: data.keyPair.publicKey.toString('hex'), senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey) }; - return transaction; - }, - - createSendTransaction: function (data) { - data.transactionType = constants.transactionTypes.SEND; - var transaction = this.createBasicTransaction(data); - transaction.asset = {}; - transaction.recipientId = data.recipientId; - transaction.amount = data.amount; - transaction.signature = this.transactionSign(transaction, data.keyPair); - return transaction; - }, - - createStateTransaction: function (data) { - data.transactionType = constants.transactionTypes.STATE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { - "state": { - key: data.key, - value: data.value, - type: 0 - } - }; - transaction.recipientId = null; - transaction.amount = 0; - transaction.signature = this.transactionSign(transaction, data.keyPair); - return transaction; - }, - - createChatTransaction: function (data) { - data.transactionType = constants.transactionTypes.CHAT_MESSAGE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { - "chat": { - message: data.message, - own_message: data.own_message, - type: data.message_type - } - }; - transaction.recipientId = data.recipientId; - transaction.amount = data.amount || 0; - transaction.signature = this.transactionSign(transaction, data.keyPair); - return transaction; - }, - - createDelegateTransaction: function (data) { - data.transactionType = constants.transactionTypes.DELEGATE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { "delegate": { "username": data.username, publicKey: data.keyPair.publicKey.toString('hex') } }; - transaction.recipientId = null; - transaction.signature = this.transactionSign(transaction, data.keyPair); - return transaction; - }, - - createVoteTransaction: function (data) { - data.transactionType = constants.transactionTypes.VOTE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { "votes": data.votes }; - transaction.recipientId = transaction.senderId; - transaction.signature = this.transactionSign(transaction, data.keyPair); - return transaction; - }, - - getHash: function (trs) { - return crypto.createHash('sha256').update(this.getBytes(trs)).digest(); - }, - - getBytes: function (transaction) { - var skipSignature = false; - var skipSecondSignature = true; - var assetSize = 0; - var assetBytes = null; - - switch (transaction.type) { - case constants.transactionTypes.SEND: - break; - case constants.transactionTypes.DELEGATE: - assetBytes = this.delegatesGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.STATE: - assetBytes = this.statesGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.VOTE: - assetBytes = this.voteGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.CHAT_MESSAGE: - assetBytes = this.chatGetBytes(transaction); - assetSize = assetBytes.length; - break; - default: - // 'Not supported yet' - return 0; - } - - var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true); - - bb.writeByte(transaction.type); - bb.writeInt(transaction.timestamp); - - var senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, 'hex'); - for (var i = 0; i < senderPublicKeyBuffer.length; i++) { - bb.writeByte(senderPublicKeyBuffer[i]); - } - - if (transaction.requesterPublicKey) { - var requesterPublicKey = Buffer.from(transaction.requesterPublicKey, 'hex'); - - for (var i = 0; i < requesterPublicKey.length; i++) { - bb.writeByte(requesterPublicKey[i]); - } - } - - if (transaction.recipientId) { - var recipient = transaction.recipientId.slice(1); - recipient = new bignum(recipient).toBuffer({ size: 8 }); - - for (i = 0; i < 8; i++) { - bb.writeByte(recipient[i] || 0); - } - } else { - for (i = 0; i < 8; i++) { - bb.writeByte(0); - } - } - - bb.writeLong(transaction.amount); - - if (assetSize > 0) { - for (var i = 0; i < assetSize; i++) { - bb.writeByte(assetBytes[i]); - } - } - - if (!skipSignature && transaction.signature) { - var signatureBuffer = Buffer.from(transaction.signature, 'hex'); - for (var i = 0; i < signatureBuffer.length; i++) { - bb.writeByte(signatureBuffer[i]); - } - } - - if (!skipSecondSignature && transaction.signSignature) { - var signSignatureBuffer = Buffer.from(transaction.signSignature, 'hex'); - for (var i = 0; i < signSignatureBuffer.length; i++) { - bb.writeByte(signSignatureBuffer[i]); - } - } - - bb.flip(); - var arrayBuffer = new Uint8Array(bb.toArrayBuffer()); - var buffer = []; - - for (var i = 0; i < arrayBuffer.length; i++) { - buffer[i] = arrayBuffer[i]; - } - - return Buffer.from(buffer); - }, - - transactionSign: function (trs, keypair) { - var hash = this.getHash(trs); - return this.sign(hash, keypair).toString('hex'); - }, - - voteGetBytes: function (trs) { - var buf; - try { - buf = trs.asset.votes ? Buffer.from(trs.asset.votes.join(''), 'utf8') : null; - } catch (e) { - throw e; - } - return buf; - }, - - delegatesGetBytes: function (trs) { - if (!trs.asset.delegate.username) { - return null; - } - var buf; - - try { - buf = Buffer.from(trs.asset.delegate.username, 'utf8'); - } catch (e) { - throw e; - } - return buf; - }, - - statesGetBytes: function (trs) { - if (!trs.asset.state.value) { - return null; - } - var buf; - - try { - buf = Buffer.from([]); - var stateBuf = Buffer.from(trs.asset.state.value); - buf = Buffer.concat([buf, stateBuf]); - if (trs.asset.state.key) { - var keyBuf = Buffer.from(trs.asset.state.key); - buf = Buffer.concat([buf, keyBuf]); - } - - var bb = new ByteBuffer(4 + 4, true); - bb.writeInt(trs.asset.state.type); - bb.flip(); - - buf = Buffer.concat([buf, bb.toBuffer()]); - } catch (e) { - throw e; - } - - return buf; - }, - - chatGetBytes: function (trs) { - var buf; - - try { - buf = Buffer.from([]); - var messageBuf = Buffer.from(trs.asset.chat.message, 'hex'); - buf = Buffer.concat([buf, messageBuf]); - - if (trs.asset.chat.own_message) { - var ownMessageBuf = Buffer.from(trs.asset.chat.own_message, 'hex'); - buf = Buffer.concat([buf, ownMessageBuf]); - } - var bb = new ByteBuffer(4 + 4, true); - bb.writeInt(trs.asset.chat.type); - bb.flip(); - buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); - } catch (e) { - throw e; - } - - return buf; - }, - - sign: function (hash, keypair) { - return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex')); - } + createTransaction: function (type, data) { + switch (type) { + case constants.transactionTypes.SEND: + return this.createSendTransaction(data); + case constants.transactionTypes.VOTE: + return this.createVoteTransaction(data); + case constants.transactionTypes.DELEGATE: + return this.createDelegateTransaction(data); + case constants.transactionTypes.CHAT_MESSAGE: + return this.createChatTransaction(data); + case constants.transactionTypes.STATE: + return this.createStateTransaction(data); + } + return {}; + }, + + createBasicTransaction: function (data) { + var transaction = { type: data.transactionType, amount: 0, timestamp: time.getTime(), asset: {}, senderPublicKey: data.keyPair.publicKey.toString('hex'), senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey) }; + return transaction; + }, + + createSendTransaction: function (data) { + data.transactionType = constants.transactionTypes.SEND; + var transaction = this.createBasicTransaction(data); + transaction.asset = {}; + transaction.recipientId = data.recipientId; + transaction.amount = data.amount; + transaction.signature = this.transactionSign(transaction, data.keyPair); + return transaction; + }, + + createStateTransaction: function (data) { + data.transactionType = constants.transactionTypes.STATE; + var transaction = this.createBasicTransaction(data); + transaction.asset = { + "state": { + key: data.key, + value: data.value, + type: 0 + } + }; + transaction.recipientId = null; + transaction.amount = 0; + transaction.signature = this.transactionSign(transaction, data.keyPair); + return transaction; + }, + + createChatTransaction: function (data) { + data.transactionType = constants.transactionTypes.CHAT_MESSAGE; + var transaction = this.createBasicTransaction(data); + transaction.asset = { + "chat": { + message: data.message, + own_message: data.own_message, + type: data.message_type + } + }; + transaction.recipientId = data.recipientId; + transaction.amount = data.amount || 0; + transaction.signature = this.transactionSign(transaction, data.keyPair); + return transaction; + }, + + createDelegateTransaction: function (data) { + data.transactionType = constants.transactionTypes.DELEGATE; + var transaction = this.createBasicTransaction(data); + transaction.asset = { "delegate": { "username": data.username, publicKey: data.keyPair.publicKey.toString('hex') } }; + transaction.recipientId = null; + transaction.signature = this.transactionSign(transaction, data.keyPair); + return transaction; + }, + + createVoteTransaction: function (data) { + data.transactionType = constants.transactionTypes.VOTE; + var transaction = this.createBasicTransaction(data); + transaction.asset = { "votes": data.votes }; + transaction.recipientId = transaction.senderId; + transaction.signature = this.transactionSign(transaction, data.keyPair); + return transaction; + }, + + getHash: function (trs) { + return crypto.createHash('sha256').update(this.getBytes(trs)).digest(); + }, + + getBytes: function (transaction) { + var skipSignature = false; + var skipSecondSignature = true; + var assetSize = 0; + var assetBytes = null; + + switch (transaction.type) { + case constants.transactionTypes.SEND: + break; + case constants.transactionTypes.DELEGATE: + assetBytes = this.delegatesGetBytes(transaction); + assetSize = assetBytes.length; + break; + case constants.transactionTypes.STATE: + assetBytes = this.statesGetBytes(transaction); + assetSize = assetBytes.length; + break; + case constants.transactionTypes.VOTE: + assetBytes = this.voteGetBytes(transaction); + assetSize = assetBytes.length; + break; + case constants.transactionTypes.CHAT_MESSAGE: + assetBytes = this.chatGetBytes(transaction); + assetSize = assetBytes.length; + break; + default: + // 'Not supported yet' + return 0; + } + + var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true); + + bb.writeByte(transaction.type); + bb.writeInt(transaction.timestamp); + + var senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, 'hex'); + for (var i = 0; i < senderPublicKeyBuffer.length; i++) { + bb.writeByte(senderPublicKeyBuffer[i]); + } + + if (transaction.requesterPublicKey) { + var requesterPublicKey = Buffer.from(transaction.requesterPublicKey, 'hex'); + + for (var i = 0; i < requesterPublicKey.length; i++) { + bb.writeByte(requesterPublicKey[i]); + } + } + + if (transaction.recipientId) { + var recipient = transaction.recipientId.slice(1); + recipient = new bignum(recipient).toBuffer({ size: 8 }); + + for (i = 0; i < 8; i++) { + bb.writeByte(recipient[i] || 0); + } + } else { + for (i = 0; i < 8; i++) { + bb.writeByte(0); + } + } + + bb.writeLong(transaction.amount); + + if (assetSize > 0) { + for (var i = 0; i < assetSize; i++) { + bb.writeByte(assetBytes[i]); + } + } + + if (!skipSignature && transaction.signature) { + var signatureBuffer = Buffer.from(transaction.signature, 'hex'); + for (var i = 0; i < signatureBuffer.length; i++) { + bb.writeByte(signatureBuffer[i]); + } + } + + if (!skipSecondSignature && transaction.signSignature) { + var signSignatureBuffer = Buffer.from(transaction.signSignature, 'hex'); + for (var i = 0; i < signSignatureBuffer.length; i++) { + bb.writeByte(signSignatureBuffer[i]); + } + } + + bb.flip(); + var arrayBuffer = new Uint8Array(bb.toArrayBuffer()); + var buffer = []; + + for (var i = 0; i < arrayBuffer.length; i++) { + buffer[i] = arrayBuffer[i]; + } + + return Buffer.from(buffer); + }, + + transactionSign: function (trs, keypair) { + var hash = this.getHash(trs); + return this.sign(hash, keypair).toString('hex'); + }, + + voteGetBytes: function (trs) { + var buf; + try { + buf = trs.asset.votes ? Buffer.from(trs.asset.votes.join(''), 'utf8') : null; + } catch (e) { + throw e; + } + return buf; + }, + + delegatesGetBytes: function (trs) { + if (!trs.asset.delegate.username) { + return null; + } + var buf; + + try { + buf = Buffer.from(trs.asset.delegate.username, 'utf8'); + } catch (e) { + throw e; + } + return buf; + }, + + statesGetBytes: function (trs) { + if (!trs.asset.state.value) { + return null; + } + var buf; + + try { + buf = Buffer.from([]); + var stateBuf = Buffer.from(trs.asset.state.value); + buf = Buffer.concat([buf, stateBuf]); + if (trs.asset.state.key) { + var keyBuf = Buffer.from(trs.asset.state.key); + buf = Buffer.concat([buf, keyBuf]); + } + + var bb = new ByteBuffer(4 + 4, true); + bb.writeInt(trs.asset.state.type); + bb.flip(); + + buf = Buffer.concat([buf, bb.toBuffer()]); + } catch (e) { + throw e; + } + + return buf; + }, + + chatGetBytes: function (trs) { + var buf; + + try { + buf = Buffer.from([]); + var messageBuf = Buffer.from(trs.asset.chat.message, 'hex'); + buf = Buffer.concat([buf, messageBuf]); + + if (trs.asset.chat.own_message) { + var ownMessageBuf = Buffer.from(trs.asset.chat.own_message, 'hex'); + buf = Buffer.concat([buf, ownMessageBuf]); + } + var bb = new ByteBuffer(4 + 4, true); + bb.writeInt(trs.asset.chat.type); + bb.flip(); + buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); + } catch (e) { + throw e; + } + + return buf; + }, + + sign: function (hash, keypair) { + return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex')); + } }; diff --git a/src/helpers/validator.js b/src/helpers/validator.js index d1df0a8..a021e69 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -26,28 +26,28 @@ module.exports = { validatePassPhrase(passPhrase) { if (!passPhrase || typeof(passPhrase) !== 'string' || passPhrase.length < 30) - return false + return false else return true }, validateEndpoint(endpoint) { if (!endpoint || typeof(endpoint) !== 'string') - return false + return false else return true }, validateAdmAddress(address) { if (!address || typeof(address) !== 'string' || !constants.RE_ADM_ADDRESS.test(address)) - return false + return false else return true }, validateAdmPublicKey(publicKey) { if (!publicKey || typeof(publicKey) !== 'string' || publicKey.length !== 64 || !constants.RE_HEX.test(publicKey)) - return false + return false else return true }, @@ -66,28 +66,28 @@ module.exports = { validateIntegerAmount(amount) { if (!amount || typeof(amount) !== 'number' || isNaN(amount) || !Number.isSafeInteger(amount)) - return false + return false else return true }, validateStringAmount(amount) { if (!amount || !this.isNumeric(amount)) - return false + return false else return true }, validateMessageType(message_type) { if (!message_type || typeof(message_type) !== 'number' || ![1,2,3].includes(message_type)) - return false + return false else return true }, validateMessage(message, message_type) { if (typeof(message) !== 'string') - return { + return { result: false, error: `Message must be a string` } diff --git a/src/helpers/wsClient.js b/src/helpers/wsClient.js index 05853b7..8895796 100644 --- a/src/helpers/wsClient.js +++ b/src/helpers/wsClient.js @@ -3,98 +3,98 @@ const logger = require('./logger'); const validator = require('./validator'); module.exports = { - - isSocketEnabled: false, // If we need socket connection - wsType: "ws", // Socket connection type, "ws" (default) or "wss" - admAddress: '', // ADM address to subscribe to notifications - connection: null, // Socket connection - onNewMessage: null, // Method to process new messages or transactions - activeNodes: [], // List of nodes that are active. Not all of them synced and support socket. - activeSocketNodes: [], // List of nodes that are active, synced and support socket - useFastest: false, // If to connect to node with minimum ping. Not recommended. - // Constructor - initSocket(params) { - this.onNewMessage = params.onNewMessage; - this.isSocketEnabled = params.socket; - this.wsType = params.wsType; - this.admAddress = params.admAddress; - }, + isSocketEnabled: false, // If we need socket connection + wsType: "ws", // Socket connection type, "ws" (default) or "wss" + admAddress: '', // ADM address to subscribe to notifications + connection: null, // Socket connection + onNewMessage: null, // Method to process new messages or transactions + activeNodes: [], // List of nodes that are active. Not all of them synced and support socket. + activeSocketNodes: [], // List of nodes that are active, synced and support socket + useFastest: false, // If to connect to node with minimum ping. Not recommended. - // Runs after every healthCheck() to re-connect socket if needed - reviseConnection(nodes) { - if (!this.isSocketEnabled) { - return; - } - if (!this.connection || !this.connection.connected) { - this.activeNodes = nodes.slice(); - this.setNodes(); - this.setConnection(); - } - }, + // Constructor + initSocket(params) { + this.onNewMessage = params.onNewMessage; + this.isSocketEnabled = params.socket; + this.wsType = params.wsType; + this.admAddress = params.admAddress; + }, - // Make socket connection and subscribe to new transactions - setConnection() { - if (this.activeSocketNodes.length === 0) { - logger.warn(`[Socket] No supported socket nodes at the moment.`); - return; - } + // Runs after every healthCheck() to re-connect socket if needed + reviseConnection(nodes) { + if (!this.isSocketEnabled) { + return; + } + if (!this.connection || !this.connection.connected) { + this.activeNodes = nodes.slice(); + this.setNodes(); + this.setConnection(); + } + }, - const node = this.socketAddress(); - logger.log(`[Socket] Supported nodes: ${this.activeSocketNodes.length}. Connecting to ${node}...`); - this.connection = ioClient.connect(node, { reconnection: false, timeout: 5000 }); + // Make socket connection and subscribe to new transactions + setConnection() { + if (this.activeSocketNodes.length === 0) { + logger.warn(`[Socket] No supported socket nodes at the moment.`); + return; + } - this.connection.on('connect', () => { - this.connection.emit('address', this.admAddress); - logger.info('[Socket] Connected to ' + node + ' and subscribed to incoming transactions for ' + this.admAddress + '.'); - }); + const node = this.socketAddress(); + logger.log(`[Socket] Supported nodes: ${this.activeSocketNodes.length}. Connecting to ${node}...`); + this.connection = ioClient.connect(node, { reconnection: false, timeout: 5000 }); - this.connection.on('disconnect', reason => { - logger.warn('[Socket] Disconnected. Reason: ' + reason) - }); + this.connection.on('connect', () => { + this.connection.emit('address', this.admAddress); + logger.info('[Socket] Connected to ' + node + ' and subscribed to incoming transactions for ' + this.admAddress + '.'); + }); - this.connection.on('connect_error', (err) => { - logger.warn('[Socket] Connection error: ' + err) - }); + this.connection.on('disconnect', reason => { + logger.warn('[Socket] Disconnected. Reason: ' + reason) + }); - this.connection.on('newTrans', transaction => { - if ((transaction.recipientId === this.admAddress) && (transaction.type === 0 || transaction.type === 8)) { - // console.info(`[Socket] New incoming socket transaction received: ${transaction.id}`); - this.onNewMessage(transaction); - } - }); - }, + this.connection.on('connect_error', (err) => { + logger.warn('[Socket] Connection error: ' + err) + }); - // Save the list of nodes activeSocketNodes that are active, synced and support socket - setNodes() { - this.activeSocketNodes = this.activeNodes.filter(n => n.socketSupport & !n.outOfSync); - // Remove nodes without IP if "ws" connection type - if (this.wsType === "ws") { - this.activeSocketNodes = this.activeSocketNodes.filter(n => !n.ifHttps || n.ip); - } - }, + this.connection.on('newTrans', transaction => { + if ((transaction.recipientId === this.admAddress) && (transaction.type === 0 || transaction.type === 8)) { + // console.info(`[Socket] New incoming socket transaction received: ${transaction.id}`); + this.onNewMessage(transaction); + } + }); + }, - // Returns socket url for connection - socketAddress() { - const node = this.useFastest ? this.fastestNode() : this.randomNode(); - let socketUrl = this.wsType + "://"; - if (this.wsType === "ws") { - let host = node.ip; - if (!host || host === undefined) - host = node.url; - socketUrl = socketUrl + host + ":" + node.wsPort - } else { - socketUrl = socketUrl + node.url; // no port if wss - } - return socketUrl; - }, + // Save the list of nodes activeSocketNodes that are active, synced and support socket + setNodes() { + this.activeSocketNodes = this.activeNodes.filter(n => n.socketSupport & !n.outOfSync); + // Remove nodes without IP if "ws" connection type + if (this.wsType === "ws") { + this.activeSocketNodes = this.activeSocketNodes.filter(n => !n.ifHttps || n.ip); + } + }, - fastestNode() { - return this.activeSocketNodes[0]; // They are sorted by ping - }, + // Returns socket url for connection + socketAddress() { + const node = this.useFastest ? this.fastestNode() : this.randomNode(); + let socketUrl = this.wsType + "://"; + if (this.wsType === "ws") { + let host = node.ip; + if (!host || host === undefined) + host = node.url; + socketUrl = socketUrl + host + ":" + node.wsPort + } else { + socketUrl = socketUrl + node.url; // no port if wss + } + return socketUrl; + }, - randomNode() { - return this.activeSocketNodes[validator.getRandomIntInclusive(0, this.activeSocketNodes.length - 1)] - } + fastestNode() { + return this.activeSocketNodes[0]; // They are sorted by ping + }, + + randomNode() { + return this.activeSocketNodes[validator.getRandomIntInclusive(0, this.activeSocketNodes.length - 1)] + } } diff --git a/src/index.js b/src/index.js index 7a2e1ef..fe160aa 100644 --- a/src/index.js +++ b/src/index.js @@ -19,27 +19,27 @@ const socket = require('./helpers/wsClient'); const logger = require('./helpers/logger'); module.exports = (params, log) => { - log = log || console; - logger.initLogger(params.logLevel, log); - const nodeManager = healthCheck(params.node, params.checkHealthAtStartup); + log = log || console; + logger.initLogger(params.logLevel, log); + const nodeManager = healthCheck(params.node, params.checkHealthAtStartup); - return { - get: get(nodeManager), - getPublicKey: getPublicKey(nodeManager), - sendTokens: sendTokens(nodeManager), - sendMessage: sendMessage(nodeManager), - newDelegate: newDelegate(nodeManager), - voteForDelegate: voteForDelegate(nodeManager), - decodeMsg, - eth, - dash, - btc, - doge, - lsk, - transactionFormer, - keys, - encryptor, - socket, - constants - }; + return { + get: get(nodeManager), + getPublicKey: getPublicKey(nodeManager), + sendTokens: sendTokens(nodeManager), + sendMessage: sendMessage(nodeManager), + newDelegate: newDelegate(nodeManager), + voteForDelegate: voteForDelegate(nodeManager), + decodeMsg, + eth, + dash, + btc, + doge, + lsk, + transactionFormer, + keys, + encryptor, + socket, + constants + }; }; From b7070759a99cad5b791f7a802fc32eed38dc1b39 Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 25 Mar 2022 19:04:16 +0600 Subject: [PATCH 09/31] refactor(index.js): fix ESLint errors and more --- src/index.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index fe160aa..bdf9495 100644 --- a/src/index.js +++ b/src/index.js @@ -1,26 +1,32 @@ -const constants = require('./helpers/constants.js'); const get = require('./groups/get'); const getPublicKey = require('./groups/getPublicKey'); const decodeMsg = require('./groups/decodeMsg'); + const newDelegate = require('./groups/newDelegate'); const voteForDelegate = require('./groups/voteForDelegate'); const sendTokens = require('./groups/sendTokens'); const sendMessage = require('./groups/sendMessage'); const healthCheck = require('./helpers/healthCheck'); + const eth = require('./groups/eth'); const dash = require('./groups/dash'); const btc = require('./groups/btc'); const doge = require('./groups/doge'); const lsk = require('./groups/lsk'); -const transactionFormer = require('./helpers/transactionFormer'); -const keys = require('./helpers/keys'); + const encryptor = require('./helpers/encryptor'); const socket = require('./helpers/wsClient'); const logger = require('./helpers/logger'); -module.exports = (params, log) => { - log = log || console; +const constants = require('./helpers/constants.js'); +const transactionFormer = require('./helpers/transactionFormer'); +const keys = require('./helpers/keys'); + +module.exports = (params, customLogger) => { + const log = customLogger || console; + logger.initLogger(params.logLevel, log); + const nodeManager = healthCheck(params.node, params.checkHealthAtStartup); return { @@ -40,6 +46,6 @@ module.exports = (params, log) => { keys, encryptor, socket, - constants + constants, }; }; From 6f32cb7ea88853a4ec54723cd7114deede44a5fb Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 25 Mar 2022 19:10:40 +0600 Subject: [PATCH 10/31] refactor: autofix ESLint errors --- src/groups/btc.js | 17 ++-- src/groups/coinNetworks.js | 4 +- src/groups/dash.js | 17 ++-- src/groups/decodeMsg.js | 30 +++---- src/groups/doge.js | 17 ++-- src/groups/eth.js | 12 +-- src/groups/get.js | 58 ++++++------- src/groups/getPublicKey.js | 17 ++-- src/groups/lsk.js | 14 ++-- src/groups/newDelegate.js | 12 ++- src/groups/sendMessage.js | 120 ++++++++++++++------------- src/groups/sendTokens.js | 70 ++++++++-------- src/groups/voteForDelegate.js | 19 +++-- src/helpers/axiosClient.js | 4 +- src/helpers/bignumber.js | 124 ++++++++++++++-------------- src/helpers/constants.js | 8 +- src/helpers/encryptor.js | 42 +++++----- src/helpers/healthCheck.js | 111 ++++++++++++------------- src/helpers/keys.js | 36 ++++----- src/helpers/logger.js | 22 +++-- src/helpers/time.js | 12 +-- src/helpers/transactionFormer.js | 118 +++++++++++++-------------- src/helpers/validator.js | 135 ++++++++++++++++--------------- src/helpers/wsClient.js | 51 +++++------- 24 files changed, 526 insertions(+), 544 deletions(-) diff --git a/src/groups/btc.js b/src/groups/btc.js index 601023f..40c4771 100644 --- a/src/groups/btc.js +++ b/src/groups/btc.js @@ -1,6 +1,6 @@ -var bitcoin = require('bitcoinjs-lib'); -var coinNetworks = require('./coinNetworks'); -const btc = { } +const bitcoin = require('bitcoinjs-lib'); +const coinNetworks = require('./coinNetworks'); +const btc = { }; /** * Generates a BTC account from the passphrase specified. @@ -8,20 +8,19 @@ const btc = { } * @returns {object} network info, keyPair, privateKey, privateKeyWIF */ -btc.keys = passphrase => { +btc.keys = (passphrase) => { const network = coinNetworks.BTC; const pwHash = bitcoin.crypto.sha256(Buffer.from(passphrase)); - const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, { network }); + const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, {network}); return { network, keyPair, - address: bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network }).address, + address: bitcoin.payments.p2pkh({pubkey: keyPair.publicKey, network}).address, // BTC private key is a regular 256-bit key privateKey: keyPair.privateKey.toString('hex'), // regular 256-bit (32 bytes, 64 characters) private key - privateKeyWIF: keyPair.toWIF() // Wallet Import Format (52 base58 characters) - } - + privateKeyWIF: keyPair.toWIF(), // Wallet Import Format (52 base58 characters) + }; }; module.exports = btc; diff --git a/src/groups/coinNetworks.js b/src/groups/coinNetworks.js index 3a42b31..a70f64e 100644 --- a/src/groups/coinNetworks.js +++ b/src/groups/coinNetworks.js @@ -8,6 +8,6 @@ module.exports = { name: 'Lisk', port: 8000, wsPort: 8001, - unit: 'LSK' + unit: 'LSK', }, -} +}; diff --git a/src/groups/dash.js b/src/groups/dash.js index fc95ee8..80daec5 100644 --- a/src/groups/dash.js +++ b/src/groups/dash.js @@ -1,6 +1,6 @@ -var bitcoin = require('bitcoinjs-lib'); -var coinNetworks = require('./coinNetworks'); -const dash = { } +const bitcoin = require('bitcoinjs-lib'); +const coinNetworks = require('./coinNetworks'); +const dash = { }; /** * Generates a DASH account from the passphrase specified. @@ -8,20 +8,19 @@ const dash = { } * @returns {object} network info, keyPair, privateKey, privateKeyWIF */ -dash.keys = passphrase => { +dash.keys = (passphrase) => { const network = coinNetworks.DASH; const pwHash = bitcoin.crypto.sha256(Buffer.from(passphrase)); - const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, { network }); + const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, {network}); return { network, keyPair, - address: bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network }).address, + address: bitcoin.payments.p2pkh({pubkey: keyPair.publicKey, network}).address, // DASH private key is a regular 256-bit key privateKey: keyPair.privateKey.toString('hex'), // regular 256-bit (32 bytes, 64 characters) private key - privateKeyWIF: keyPair.toWIF() // Wallet Import Format (52 base58 characters) - } - + privateKeyWIF: keyPair.toWIF(), // Wallet Import Format (52 base58 characters) + }; }; module.exports = dash; diff --git a/src/groups/decodeMsg.js b/src/groups/decodeMsg.js index 797ffaf..da25f36 100644 --- a/src/groups/decodeMsg.js +++ b/src/groups/decodeMsg.js @@ -3,48 +3,43 @@ const nacl = require('tweetnacl/nacl-fast'); const keys = require('../helpers/keys'); module.exports = (msg, senderPublicKey, passPhrase, nonce) => { - const keypair = keys.createKeypairFromPassPhrase(passPhrase); let privateKey = keypair.privateKey; if (typeof msg === 'string') { - msg = hexToBytes(msg) + msg = hexToBytes(msg); } if (typeof nonce === 'string') { - nonce = hexToBytes(nonce) + nonce = hexToBytes(nonce); } if (typeof senderPublicKey === 'string') { - senderPublicKey = hexToBytes(senderPublicKey) + senderPublicKey = hexToBytes(senderPublicKey); } if (typeof privateKey === 'string') { - privateKey = hexToBytes(privateKey) + privateKey = hexToBytes(privateKey); } const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey); const DHSecretKey = ed2curve.convertSecretKey(privateKey); const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey); - return decrypted ? Utf8ArrayToStr(decrypted) : '' - -} + return decrypted ? Utf8ArrayToStr(decrypted) : ''; +}; function hexToBytes(hexString = '') { - - const bytes = [] + const bytes = []; for (let c = 0; c < hexString.length; c += 2) { - bytes.push(parseInt(hexString.substr(c, 2), 16)) + bytes.push(parseInt(hexString.substr(c, 2), 16)); } return Uint8Array.from(bytes); - } function Utf8ArrayToStr(array) { + let out; let i; let len; let c; + let char2; let char3; - var out, i, len, c; - var char2, char3; - - out = ""; + out = ''; len = array.length; i = 0; while (i < len) { @@ -79,5 +74,4 @@ function Utf8ArrayToStr(array) { } return out; - -} \ No newline at end of file +} diff --git a/src/groups/doge.js b/src/groups/doge.js index 08eca09..ceb0ad5 100644 --- a/src/groups/doge.js +++ b/src/groups/doge.js @@ -1,6 +1,6 @@ -var bitcoin = require('bitcoinjs-lib'); -var coinNetworks = require('./coinNetworks'); -const doge = { } +const bitcoin = require('bitcoinjs-lib'); +const coinNetworks = require('./coinNetworks'); +const doge = { }; /** * Generates a DOGE account from the passphrase specified. @@ -8,20 +8,19 @@ const doge = { } * @returns {object} network info, keyPair, privateKey, privateKeyWIF */ -doge.keys = passphrase => { +doge.keys = (passphrase) => { const network = coinNetworks.DOGE; const pwHash = bitcoin.crypto.sha256(Buffer.from(passphrase)); - const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, { network }); + const keyPair = bitcoin.ECPair.fromPrivateKey(pwHash, {network}); return { network, keyPair, - address: bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network }).address, + address: bitcoin.payments.p2pkh({pubkey: keyPair.publicKey, network}).address, // DOGE private key is a regular 256-bit key privateKey: keyPair.privateKey.toString('hex'), // regular 256-bit (32 bytes, 64 characters) private key - privateKeyWIF: keyPair.toWIF() // Wallet Import Format (52 base58 characters) - } - + privateKeyWIF: keyPair.toWIF(), // Wallet Import Format (52 base58 characters) + }; }; module.exports = doge; diff --git a/src/groups/eth.js b/src/groups/eth.js index 62e4a6e..524f43e 100644 --- a/src/groups/eth.js +++ b/src/groups/eth.js @@ -1,8 +1,8 @@ -var Mnemonic = require('bitcore-mnemonic'); +const Mnemonic = require('bitcore-mnemonic'); const hdkey = require('hdkey'); -const HD_KEY_PATH = "m/44'/60'/3'/1/0"; -const { bufferToHex, privateToAddress } = require('ethereumjs-util'); -const eth = { } +const HD_KEY_PATH = 'm/44\'/60\'/3\'/1/0'; +const {bufferToHex, privateToAddress} = require('ethereumjs-util'); +const eth = { }; /** * Generates a ETH account from the passphrase specified. @@ -10,14 +10,14 @@ const eth = { } * @returns {{address: String, privateKey: Buffer}} */ -eth.keys = passphrase => { +eth.keys = (passphrase) => { const mnemonic = new Mnemonic(passphrase, Mnemonic.Words.ENGLISH); const seed = mnemonic.toSeed(); const privateKey = hdkey.fromMasterSeed(seed).derive(HD_KEY_PATH)._privateKey; return { address: bufferToHex(privateToAddress(privateKey)), - privateKey: bufferToHex(privateKey) + privateKey: bufferToHex(privateKey), }; }; diff --git a/src/groups/get.js b/src/groups/get.js index ead691e..8090efa 100644 --- a/src/groups/get.js +++ b/src/groups/get.js @@ -6,41 +6,43 @@ const DEFAULT_GET_REQUEST_RETRIES = 3; // How much re-tries for get-requests by module.exports = (nodeManager) => { return (endpoint, params, maxRetries = DEFAULT_GET_REQUEST_RETRIES, retryNo = 0) => { - - let url = trimAny(endpoint, "/ ").replace(/^api\//, ''); - if (!url || !validator.validateEndpoint(endpoint)) - return validator.badParameter('endpoint') + let url = trimAny(endpoint, '/ ').replace(/^api\//, ''); + if (!url || !validator.validateEndpoint(endpoint)) { + return validator.badParameter('endpoint'); + } url = nodeManager.node() + '/api/' + url; - return axios.get(url, { params }) - .then(function (response) { - return validator.formatRequestResults(response, true) - }) - .catch(function (error) { - let logMessage = `[ADAMANT js-api] Get-request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; - if (retryNo < maxRetries) { - logger.log(`${logMessage} Retrying…`); - return nodeManager.changeNodes() - .then(function () { - return module.exports(nodeManager)(endpoint, params, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); - return validator.formatRequestResults(error, false) - }) - - } + return axios.get(url, {params}) + .then(function(response) { + return validator.formatRequestResults(response, true); + }) + .catch(function(error) { + const logMessage = `[ADAMANT js-api] Get-request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .then(function() { + return module.exports(nodeManager)(endpoint, params, maxRetries, ++retryNo); + }); + } + logger.warn(`${logMessage} No more attempts, returning error.`); + return validator.formatRequestResults(error, false); + }); + }; }; function trimAny(str, chars) { - if (!str || typeof str !== 'string') - return '' - let start = 0, - end = str.length; - while (start < end && chars.indexOf(str[start]) >= 0) + if (!str || typeof str !== 'string') { + return ''; + } + let start = 0; + let end = str.length; + while (start < end && chars.indexOf(str[start]) >= 0) { ++start; - while (end > start && chars.indexOf(str[end - 1]) >= 0) + } + while (end > start && chars.indexOf(str[end - 1]) >= 0) { --end; + } return (start > 0 || end < str.length) ? str.substring(start, end) : str; } diff --git a/src/groups/getPublicKey.js b/src/groups/getPublicKey.js index dc40b21..1ce213f 100644 --- a/src/groups/getPublicKey.js +++ b/src/groups/getPublicKey.js @@ -3,19 +3,18 @@ const logger = require('../helpers/logger'); const publicKeysCache = { }; module.exports = (nodeManager) => { - return async (address) => { - if (publicKeysCache[address]) - return publicKeysCache[address] - - const publicKey = await get(nodeManager)('/accounts/getPublicKey', { address }); + if (publicKeysCache[address]) { + return publicKeysCache[address]; + } + + const publicKey = await get(nodeManager)('/accounts/getPublicKey', {address}); if (publicKey.success) { publicKeysCache[address] = publicKey.data.publicKey; - return publicKey.data.publicKey + return publicKey.data.publicKey; } else { logger.warn(`[ADAMANT js-api] Failed to get public key for ${address}. ${publicKey.errorMessage}.`); - return false + return false; } - } - + }; }; diff --git a/src/groups/lsk.js b/src/groups/lsk.js index 51f467b..9d2ea53 100644 --- a/src/groups/lsk.js +++ b/src/groups/lsk.js @@ -1,5 +1,5 @@ -const cryptography = require('@liskhq/lisk-cryptography') -const sodium = require('sodium-browserify-tweetnacl') +const cryptography = require('@liskhq/lisk-cryptography'); +const sodium = require('sodium-browserify-tweetnacl'); const pbkdf2 = require('pbkdf2'); const coinNetworks = require('./coinNetworks'); @@ -11,8 +11,8 @@ const LiskHashSettings = { SALT: 'adm', ITERATIONS: 2048, KEYLEN: 32, - DIGEST: 'sha256' -} + DIGEST: 'sha256', +}; /** * Generates a LSK account from the passphrase specified. @@ -20,7 +20,7 @@ const LiskHashSettings = { * @returns {object} network info, keyPair, address, addressHexBinary, addressHex, privateKey */ -lsk.keys = passphrase => { +lsk.keys = (passphrase) => { const network = coinNetworks.LSK; const liskSeed = pbkdf2.pbkdf2Sync(passphrase, LiskHashSettings.SALT, LiskHashSettings.ITERATIONS, LiskHashSettings.KEYLEN, LiskHashSettings.DIGEST); const keyPair = sodium.crypto_sign_seed_keypair(liskSeed); @@ -35,8 +35,8 @@ lsk.keys = passphrase => { address, addressHexBinary, addressHex, - privateKey - } + privateKey, + }; }; module.exports = lsk; diff --git a/src/groups/newDelegate.js b/src/groups/newDelegate.js index 9d94d7b..8b802da 100644 --- a/src/groups/newDelegate.js +++ b/src/groups/newDelegate.js @@ -15,10 +15,9 @@ module.exports = (nodeManager) => { * It must be unique in ADAMANT blockchain. It should not be similar to ADAMANT address. * Delegate name can only contain alphanumeric characters and symbols !@$&_. * @param {number} maxRetries How much times to retry request - * @returns {Promise} Request results + * @return {Promise} Request results */ return async (passPhrase, username, maxRetries = DEFAULT_NEW_DELEGATE_RETRIES, retryNo = 0) => { - let transaction; try { @@ -41,7 +40,6 @@ module.exports = (nodeManager) => { }; transaction = transactionFormer.createTransaction(type, data); - } catch (e) { return validator.badParameter('#exception_catched#', e); } @@ -59,14 +57,14 @@ module.exports = (nodeManager) => { logger.log(`${logMessage} Retrying…`); return nodeManager.changeNodes() - .then(() => ( - module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - )); + .then(() => ( + module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) + )); } logger.warn(`${logMessage} No more attempts, returning error.`); return validator.formatRequestResults(error, false); } - } + }; }; diff --git a/src/groups/sendMessage.js b/src/groups/sendMessage.js index 156fd5f..04f7344 100644 --- a/src/groups/sendMessage.js +++ b/src/groups/sendMessage.js @@ -26,118 +26,116 @@ module.exports = (nodeManager) => { * @returns {Promise} Request results */ return async (passPhrase, addressOrPublicKey, message, message_type = 'basic', amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_MESSAGE_RETRIES, retryNo = 0) => { - - let keyPair, data; - let address, publicKey; + let keyPair; let data; + let address; let publicKey; try { - - if (!validator.validatePassPhrase(passPhrase)) - return validator.badParameter('passPhrase') + if (!validator.validatePassPhrase(passPhrase)) { + return validator.badParameter('passPhrase'); + } keyPair = keys.createKeypairFromPassPhrase(passPhrase); if (!validator.validateAdmAddress(addressOrPublicKey)) { if (!validator.validateAdmPublicKey(addressOrPublicKey)) { - return validator.badParameter('addressOrPublicKey', addressOrPublicKey) + return validator.badParameter('addressOrPublicKey', addressOrPublicKey); } else { publicKey = addressOrPublicKey; try { - address = keys.createAddressFromPublicKey(publicKey) + address = keys.createAddressFromPublicKey(publicKey); } catch (e) { - return validator.badParameter('addressOrPublicKey', addressOrPublicKey) + return validator.badParameter('addressOrPublicKey', addressOrPublicKey); } } } else { publicKey = ''; - address = addressOrPublicKey + address = addressOrPublicKey; } - if (message_type === 'basic') + if (message_type === 'basic') { message_type = 1; - if (message_type === 'rich') + } + if (message_type === 'rich') { message_type = 2; - if (message_type === 'signal') + } + if (message_type === 'signal') { message_type = 3; + } - if (!validator.validateMessageType(message_type)) - return validator.badParameter('message_type', message_type) + if (!validator.validateMessageType(message_type)) { + return validator.badParameter('message_type', message_type); + } - let messageValidation = validator.validateMessage(message, message_type); - if (!messageValidation.result) - return validator.badParameter('message', message, messageValidation.error) + const messageValidation = validator.validateMessage(message, message_type); + if (!messageValidation.result) { + return validator.badParameter('message', message, messageValidation.error); + } data = { keyPair, recipientId: address, - message_type + message_type, }; if (amount) { if (isAmountInADM) { - amountInSat = validator.AdmToSats(amount) + amountInSat = validator.AdmToSats(amount); } else { - amountInSat = amount + amountInSat = amount; + } + if (!validator.validateIntegerAmount(amountInSat)) { + return validator.badParameter('amount', amount); } - if (!validator.validateIntegerAmount(amountInSat)) - return validator.badParameter('amount', amount) data.amount = amountInSat; } - } catch (e) { - - return validator.badParameter('#exception_catched#', e) - + return validator.badParameter('#exception_catched#', e); } - if (!publicKey) + if (!publicKey) { publicKey = await getPublicKey(nodeManager)(address); + } - if (!publicKey) + if (!publicKey) { return new Promise((resolve, reject) => { resolve({ success: false, - errorMessage: `Unable to get public key for ${addressOrPublicKey}. It is necessary for sending an encrypted message. Account may be uninitialized (https://medium.com/adamant-im/chats-and-uninitialized-accounts-in-adamant-5035438e2fcd), or network error` - }) - }) + errorMessage: `Unable to get public key for ${addressOrPublicKey}. It is necessary for sending an encrypted message. Account may be uninitialized (https://medium.com/adamant-im/chats-and-uninitialized-accounts-in-adamant-5035438e2fcd), or network error`, + }); + }); + } try { - const encryptedMessage = encryptor.encodeMessage(message, keyPair, publicKey); data.message = encryptedMessage.message; data.own_message = encryptedMessage.own_message; - let transaction = transactionFormer.createTransaction(constants.transactionTypes.CHAT_MESSAGE, data); - - let url = nodeManager.node() + '/api/transactions/process'; - return axios.post(url, { transaction }) - .then(function (response) { - return validator.formatRequestResults(response, true) - }) - .catch(function (error) { - let logMessage = `[ADAMANT js-api] Send message request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo+1} of ${maxRetries+1}.`; - if (retryNo < maxRetries) { - logger.log(`${logMessage} Retrying…`); - return nodeManager.changeNodes() - .then(function () { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, message, message_type, tokensAmount, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); - return validator.formatRequestResults(error, false) - }) - + const transaction = transactionFormer.createTransaction(constants.transactionTypes.CHAT_MESSAGE, data); + + const url = nodeManager.node() + '/api/transactions/process'; + return axios.post(url, {transaction}) + .then(function(response) { + return validator.formatRequestResults(response, true); + }) + .catch(function(error) { + const logMessage = `[ADAMANT js-api] Send message request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo+1} of ${maxRetries+1}.`; + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .then(function() { + return module.exports(nodeManager)(passPhrase, addressOrPublicKey, message, message_type, tokensAmount, maxRetries, ++retryNo); + }); + } + logger.warn(`${logMessage} No more attempts, returning error.`); + return validator.formatRequestResults(error, false); + }); } catch (e) { - return new Promise((resolve, reject) => { resolve({ success: false, - errorMessage: `Unable to encode message '${message}' with public key ${publicKey}, or unable to build a transaction. Exception: ` + e - }) - }) - + errorMessage: `Unable to encode message '${message}' with public key ${publicKey}, or unable to build a transaction. Exception: ` + e, + }); + }); } - - } // sendMessage() - + }; // sendMessage() }; diff --git a/src/groups/sendTokens.js b/src/groups/sendTokens.js index 24da993..bd35d78 100644 --- a/src/groups/sendTokens.js +++ b/src/groups/sendTokens.js @@ -20,73 +20,69 @@ module.exports = (nodeManager) => { * @returns {Promise} Request results */ return (passPhrase, addressOrPublicKey, amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { - let transaction; - let address, publicKey; + let address; let publicKey; try { - - if (!validator.validatePassPhrase(passPhrase)) - return validator.badParameter('passPhrase') + if (!validator.validatePassPhrase(passPhrase)) { + return validator.badParameter('passPhrase'); + } const keyPair = keys.createKeypairFromPassPhrase(passPhrase); if (!validator.validateAdmAddress(addressOrPublicKey)) { if (!validator.validateAdmPublicKey(addressOrPublicKey)) { - return validator.badParameter('addressOrPublicKey', addressOrPublicKey) + return validator.badParameter('addressOrPublicKey', addressOrPublicKey); } else { publicKey = addressOrPublicKey; try { - address = keys.createAddressFromPublicKey(publicKey) + address = keys.createAddressFromPublicKey(publicKey); } catch (e) { - return validator.badParameter('addressOrPublicKey', addressOrPublicKey) + return validator.badParameter('addressOrPublicKey', addressOrPublicKey); } } } else { publicKey = ''; - address = addressOrPublicKey + address = addressOrPublicKey; } if (isAmountInADM) { - amountInSat = validator.AdmToSats(amount) + amountInSat = validator.AdmToSats(amount); } else { - amountInSat = amount + amountInSat = amount; } - if (!validator.validateIntegerAmount(amountInSat)) - return validator.badParameter('amount', amount) + if (!validator.validateIntegerAmount(amountInSat)) { + return validator.badParameter('amount', amount); + } const data = { keyPair, recipientId: address, - amount: amountInSat + amount: amountInSat, }; transaction = transactionFormer.createTransaction(constants.transactionTypes.SEND, data); - } catch (e) { - - return validator.badParameter('#exception_catched#', e) - + return validator.badParameter('#exception_catched#', e); } - let url = nodeManager.node() + '/api/transactions/process'; - return axios.post(url, { transaction }) - .then(function (response) { - return validator.formatRequestResults(response, true) - }) - .catch(function (error) { - let logMessage = `[ADAMANT js-api] Send tokens request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; - if (retryNo < maxRetries) { - logger.log(`${logMessage} Retrying…`); - return nodeManager.changeNodes() - .then(function () { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); - return validator.formatRequestResults(error, false) - }) - - } + const url = nodeManager.node() + '/api/transactions/process'; + return axios.post(url, {transaction}) + .then(function(response) { + return validator.formatRequestResults(response, true); + }) + .catch(function(error) { + const logMessage = `[ADAMANT js-api] Send tokens request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo + 1} of ${maxRetries + 1}.`; + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .then(function() { + return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo); + }); + } + logger.warn(`${logMessage} No more attempts, returning error.`); + return validator.formatRequestResults(error, false); + }); + }; }; diff --git a/src/groups/voteForDelegate.js b/src/groups/voteForDelegate.js index 3393a96..f0d247d 100644 --- a/src/groups/voteForDelegate.js +++ b/src/groups/voteForDelegate.js @@ -18,10 +18,9 @@ module.exports = (nodeManager) => { * @param {string[]} votes PublicKeys, ADM addresses and delegate names for upvote and downvote. * It would be more efficient to pass publicKey, otherwise the api will make additional queries * @param {number} maxRetries How much times to retry request - * @returns {Promise} Request results + * @return {Promise} Request results */ return async (passPhrase, votes, maxRetries = DEFAULT_VOTE_FOR_DELEGATE_RETRIES, retryNo = 0) => { - let transaction; try { @@ -44,7 +43,7 @@ module.exports = (nodeManager) => { votes[i] = `${voteDirection}${cachedPublicKey}`; } else { if (validator.validateAdmVoteForAddress(vote)) { - const res = await get(nodeManager)('/accounts', { address: voteName }); + const res = await get(nodeManager)('/accounts', {address: voteName}); if (res.success) { const publicKey = res.data.account.publicKey; @@ -57,7 +56,7 @@ module.exports = (nodeManager) => { return validator.badParameter('votes'); } } else if (validator.validateAdmVoteForDelegateName(vote)) { - const res = await get(nodeManager)('/delegates/get', { username: voteName }); + const res = await get(nodeManager)('/delegates/get', {username: voteName}); if (res.success) { const publicKey = res.data.delegate.publicKey; @@ -92,7 +91,7 @@ module.exports = (nodeManager) => { transaction = transactionFormer.createTransaction(type, data); } catch (error) { - return validator.badParameter('#exception_catched#', error) + return validator.badParameter('#exception_catched#', error); } const url = nodeManager.node() + '/api/accounts/delegates'; @@ -101,21 +100,21 @@ module.exports = (nodeManager) => { const response = await axios.post(url, transaction); return validator.formatRequestResults(response, true); - } catch(error) { + } catch (error) { const logMessage = `[ADAMANT js-api] Vote for delegate request: Request to ${url} failed with ${error.response ? error.response.status : undefined} status code, ${error.toString()}${error.response && error.response.data ? '. Message: ' + error.response.data.toString().trim() : ''}. Try ${retryNo+1} of ${maxRetries+1}.`; if (retryNo < maxRetries) { logger.log(`${logMessage} Retrying…`); return nodeManager.changeNodes() - .then(() => ( - module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - )); + .then(() => ( + module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) + )); } logger.warn(`${logMessage} No more attempts, returning error.`); return validator.formatRequestResults(error, false); } - } + }; }; diff --git a/src/helpers/axiosClient.js b/src/helpers/axiosClient.js index d41cf48..d002737 100644 --- a/src/helpers/axiosClient.js +++ b/src/helpers/axiosClient.js @@ -1,4 +1,4 @@ const axios = require('axios'); -const axiosClient = axios.create() -module.exports = axiosClient +const axiosClient = axios.create(); +module.exports = axiosClient; diff --git a/src/helpers/bignumber.js b/src/helpers/bignumber.js index 1cce693..cbd6c81 100644 --- a/src/helpers/bignumber.js +++ b/src/helpers/bignumber.js @@ -1,5 +1,5 @@ /* eslint-disable no-redeclare */ -'use strict' +'use strict'; /** * Buffer functions that implements bignumber. @@ -7,7 +7,7 @@ * @requires bignumber * @constructor */ -var BigNumber = require('bignumber.js') +const BigNumber = require('bignumber.js'); /** * Creates an instance from a Buffer. @@ -16,104 +16,106 @@ var BigNumber = require('bignumber.js') * @return {ArrayBuffer} new BigNumber instance * @throws {RangeError} error description multiple of size */ -BigNumber.fromBuffer = function (buf, opts) { - if (!opts) opts = {} +BigNumber.fromBuffer = function(buf, opts) { + if (!opts) opts = {}; - var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' + const endian = {1: 'big', '-1': 'little'}[opts.endian] || opts.endian || 'big'; - var size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1) + const size = opts.size === 'auto' ? Math.ceil(buf.length) : (opts.size || 1); if (buf.length % size !== 0) { throw new RangeError('Buffer length (' + buf.length + ')' + - ' must be a multiple of size (' + size + ')' - ) + ' must be a multiple of size (' + size + ')', + ); } - var hex = [] - for (var i = 0; i < buf.length; i += size) { - var chunk = [] - for (var j = 0; j < size; j++) { - chunk.push(buf[i + (endian === 'big' ? j : (size - j - 1))]) + const hex = []; + for (let i = 0; i < buf.length; i += size) { + const chunk = []; + for (let j = 0; j < size; j++) { + chunk.push(buf[i + (endian === 'big' ? j : (size - j - 1))]); } hex.push(chunk - .map(function (c) { - return (c < 16 ? '0' : '') + c.toString(16) - }) - .join('') - ) + .map(function(c) { + return (c < 16 ? '0' : '') + c.toString(16); + }) + .join(''), + ); } - return new BigNumber(hex.join(''), 16) -} + return new BigNumber(hex.join(''), 16); +}; /** * Returns an instance as Buffer. * @param {Object} opts * @return {ArrayBuffer} new buffer | error message invalid option */ -BigNumber.prototype.toBuffer = function (opts) { +BigNumber.prototype.toBuffer = function(opts) { if (typeof opts === 'string') { - if (opts !== 'mpint') return 'Unsupported Buffer representation' + if (opts !== 'mpint') return 'Unsupported Buffer representation'; - var abs = this.abs() - var buf = abs.toBuffer({ size: 1, endian: 'big' }) - var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length - if (buf[0] & 0x80) len++ + const abs = this.abs(); + var buf = abs.toBuffer({size: 1, endian: 'big'}); + var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length; + if (buf[0] & 0x80) len++; - var ret = Buffer.alloc(4 + len) - if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0)) - if (buf[0] & 0x80) ret[4] = 0 + const ret = Buffer.alloc(4 + len); + if (len > 0) buf.copy(ret, 4 + (buf[0] & 0x80 ? 1 : 0)); + if (buf[0] & 0x80) ret[4] = 0; - ret[0] = len & (0xff << 24) - ret[1] = len & (0xff << 16) - ret[2] = len & (0xff << 8) - ret[3] = len & (0xff << 0) + ret[0] = len & (0xff << 24); + ret[1] = len & (0xff << 16); + ret[2] = len & (0xff << 8); + ret[3] = len & (0xff << 0); // Two's compliment for negative integers - var isNeg = this.lt(0) + const isNeg = this.lt(0); if (isNeg) { - for (var i = 4; i < ret.length; i++) { - ret[i] = 0xff - ret[i] + for (let i = 4; i < ret.length; i++) { + ret[i] = 0xff - ret[i]; } } - ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0) - if (isNeg) ret[ret.length - 1]++ + ret[4] = (ret[4] & 0x7f) | (isNeg ? 0x80 : 0); + if (isNeg) ret[ret.length - 1]++; - return ret + return ret; } - if (!opts) opts = {} + if (!opts) opts = {}; - var endian = { 1: 'big', '-1': 'little' }[opts.endian] || opts.endian || 'big' + const endian = {1: 'big', '-1': 'little'}[opts.endian] || opts.endian || 'big'; - var hex = this.toString(16) + let hex = this.toString(16); if (hex.charAt(0) === '-') { throw new Error( - 'Converting negative numbers to Buffers not supported yet' - ) + 'Converting negative numbers to Buffers not supported yet', + ); } - var size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1) + const size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1); - var len = Math.ceil(hex.length / (2 * size)) * size - var buf = Buffer.alloc(len) + var len = Math.ceil(hex.length / (2 * size)) * size; + var buf = Buffer.alloc(len); // Zero-pad the hex string so the chunks are all `size` long - while (hex.length < 2 * len) hex = '0' + hex - - var hx = hex - .split(new RegExp('(.{' + (2 * size) + '})')) - .filter(function (s) { return s.length > 0 }) - - hx.forEach(function (chunk, i) { - for (var j = 0; j < size; j++) { - var ix = i * size + (endian === 'big' ? j : size - j - 1) - buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16) + while (hex.length < 2 * len) hex = '0' + hex; + + const hx = hex + .split(new RegExp('(.{' + (2 * size) + '})')) + .filter(function(s) { + return s.length > 0; + }); + + hx.forEach(function(chunk, i) { + for (let j = 0; j < size; j++) { + const ix = i * size + (endian === 'big' ? j : size - j - 1); + buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16); } - }) + }); - return buf -} + return buf; +}; -module.exports = BigNumber +module.exports = BigNumber; diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 28161ce..836dba0 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -12,7 +12,7 @@ module.exports = { chat_message: 100000, profile_update: 5000000, avatar_upload: 10000000, - state_store: 100000 + state_store: 100000, }, transactionTypes: { SEND: 0, @@ -24,7 +24,7 @@ module.exports = { IN_TRANSFER: 6, OUT_TRANSFER: 7, CHAT_MESSAGE: 8, - STATE: 9 + STATE: 9, }, maxVotesPerTransaction: 33, SAT: 100000000, @@ -38,6 +38,6 @@ module.exports = { RE_BTC_ADDRESS: /^(bc1|[13])[a-km-zA-HJ-NP-Z02-9]{25,39}$/, RE_DASH_ADDRESS: /^[7X][1-9A-HJ-NP-Za-km-z]{33,}$/, RE_DOGE_ADDRESS: /^[A|D|9][A-Z0-9]([0-9a-zA-Z]{9,})$/, - RE_LSK_ADDRESS: /^[0-9]{2,21}L$/ + RE_LSK_ADDRESS: /^[0-9]{2,21}L$/, -} +}; diff --git a/src/helpers/encryptor.js b/src/helpers/encryptor.js index e51e191..981c3b0 100644 --- a/src/helpers/encryptor.js +++ b/src/helpers/encryptor.js @@ -1,35 +1,35 @@ -var sodium = require('sodium-browserify-tweetnacl') -var nacl = require('tweetnacl/nacl-fast') -var ed2curve = require('ed2curve') +const sodium = require('sodium-browserify-tweetnacl'); +const nacl = require('tweetnacl/nacl-fast'); +const ed2curve = require('ed2curve'); module.exports = { - bytesToHex: function (bytes) { + bytesToHex: function(bytes) { for (var hex = [], i = 0; i < bytes.length; i++) { - hex.push((bytes[i] >>> 4).toString(16)) - hex.push((bytes[i] & 0xF).toString(16)) + hex.push((bytes[i] >>> 4).toString(16)); + hex.push((bytes[i] & 0xF).toString(16)); } - return hex.join('') + return hex.join(''); }, - hexToBytes: function (hex) { + hexToBytes: function(hex) { for (var bytes = [], c = 0; c < hex.length; c += 2) { - bytes.push(parseInt(hex.substr(c, 2), 16)) + bytes.push(parseInt(hex.substr(c, 2), 16)); } - return bytes + return bytes; }, - encodeMessage: function (msg, keypair, recipientPublicKey) { - var nonce = Buffer.allocUnsafe(24) - sodium.randombytes(nonce) - var plainText = Buffer.from(msg.toString()) - var DHPublicKey = ed2curve.convertPublicKey(new Uint8Array(this.hexToBytes(recipientPublicKey))) - var DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey) - var encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey) + encodeMessage: function(msg, keypair, recipientPublicKey) { + const nonce = Buffer.allocUnsafe(24); + sodium.randombytes(nonce); + const plainText = Buffer.from(msg.toString()); + const DHPublicKey = ed2curve.convertPublicKey(new Uint8Array(this.hexToBytes(recipientPublicKey))); + const DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey); + const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey); return { message: this.bytesToHex(encrypted), - own_message: this.bytesToHex(nonce) - } - } + own_message: this.bytesToHex(nonce), + }; + }, -} +}; diff --git a/src/helpers/healthCheck.js b/src/helpers/healthCheck.js index 2581650..518c62f 100644 --- a/src/helpers/healthCheck.js +++ b/src/helpers/healthCheck.js @@ -8,7 +8,6 @@ const CHECK_NODES_INTERVAL = 60 * 5 * 1000; // Update active nodes every 5 minut const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced module.exports = (nodes, checkHealthAtStartup = true) => { - isCheckingNodes = false; nodesList = nodes; activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data @@ -16,7 +15,7 @@ module.exports = (nodes, checkHealthAtStartup = true) => { /** * Updates active nodes. If nodes are already updating, returns Promise of previous call - * @returns {Promise} Call changeNodes().then to do something when update complete + * @return {Promise} Call changeNodes().then to do something when update complete */ function changeNodes(isPlannedUpdate = false) { if (!isCheckingNodes) { @@ -24,55 +23,54 @@ module.exports = (nodes, checkHealthAtStartup = true) => { if (!isPlannedUpdate) { logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); } - await checkNodes(isPlannedUpdate ? false : true) - resolve(true) + await checkNodes(isPlannedUpdate ? false : true); + resolve(true); }); } - return changeNodesPromise + return changeNodesPromise; } if (checkHealthAtStartup) { - changeNodes(true) - setInterval(() => { changeNodes(true) }, CHECK_NODES_INTERVAL); + changeNodes(true); + setInterval(() => { + changeNodes(true); + }, CHECK_NODES_INTERVAL); } return { /** - * @returns {string} Current active node, f. e. http://88.198.156.44:36666 + * @return {string} Current active node, f. e. http://88.198.156.44:36666 */ node: () => { return activeNode; }, - changeNodes + changeNodes, }; - }; /** * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active */ async function checkNodes(forceChangeActiveNode) { - this.isCheckingNodes = true; this.liveNodes = []; try { - for (const n of this.nodesList) { try { - let start = unixTimestamp(); - let req = await checkNode(n + '/api/node/status'); - let url = n.replace(/^https?:\/\/(.*)$/, '$1').split(":")[0]; - let ifIP = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(url) + const start = unixTimestamp(); + const req = await checkNode(n + '/api/node/status'); + const url = n.replace(/^https?:\/\/(.*)$/, '$1').split(':')[0]; + const ifIP = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(url); if (req.status) { this.liveNodes.push({ node: n, - ifHttps: n.startsWith("https"), + ifHttps: n.startsWith('https'), ifIP, url, outOfSync: false, @@ -81,13 +79,11 @@ async function checkNodes(forceChangeActiveNode) { heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), ip: ifIP ? url : await getIP(url), socketSupport: req.status.wsClient && req.status.wsClient.enabled, - wsPort: req.status.wsClient.port + wsPort: req.status.wsClient.port, }); - } else { logger.log(`[ADAMANT js-api] Health check: Node ${n} haven't returned its status`); } - } catch (e) { logger.log(`[ADAMANT js-api] Health check: Error while checking node ${n}, ` + e); } @@ -99,32 +95,26 @@ async function checkNodes(forceChangeActiveNode) { if (!count) { logger.error(`[ADAMANT js-api] Health check: All of ${this.nodesList.length} nodes are unavailable. Check internet connection and nodes list in config.`); } else { - // Set activeNode to one that have maximum height and minimum ping if (count === 1) { - this.activeNode = this.liveNodes[0].node; - } else if (count === 2) { - const h0 = this.liveNodes[0]; const h1 = this.liveNodes[1]; this.activeNode = h0.height > h1.height ? h0.node : h1.node; // Mark node outOfSync if needed if (h0.heightEpsilon > h1.heightEpsilon) { - this.liveNodes[1].outOfSync = true + this.liveNodes[1].outOfSync = true; outOfSyncCount += 1; } else if (h0.heightEpsilon < h1.heightEpsilon) { - this.liveNodes[0].outOfSync = true + this.liveNodes[0].outOfSync = true; outOfSyncCount += 1; } - } else { - let biggestGroup = []; // Removing lodash: const groups = _.groupBy(this.liveNodes, n => n.heightEpsilon); - const groups = this.liveNodes.reduce(function (grouped, node) { - var int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded + const groups = this.liveNodes.reduce(function(grouped, node) { + const int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded if (!grouped.hasOwnProperty(int)) { grouped[int] = []; } @@ -132,69 +122,70 @@ async function checkNodes(forceChangeActiveNode) { return grouped; }, {}); - Object.keys(groups).forEach(key => { + Object.keys(groups).forEach((key) => { if (groups[key].length > biggestGroup.length) { biggestGroup = groups[key]; } }); // All the nodes from the biggestGroup list are considered to be in sync, all the others are not - this.liveNodes.forEach(node => { - node.outOfSync = !biggestGroup.includes(node) - }) + this.liveNodes.forEach((node) => { + node.outOfSync = !biggestGroup.includes(node); + }); outOfSyncCount = this.liveNodes.length - biggestGroup.length; biggestGroup.sort((a, b) => a.ping - b.ping); this.liveNodes.sort((a, b) => a.ping - b.ping); - if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) - this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node // Use random node from which are synced - else - this.activeNode = biggestGroup[0].node; // Use node with minimum ping among which are synced - + if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) { + this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node; + } // Use random node from which are synced + else { + this.activeNode = biggestGroup[0].node; + } // Use node with minimum ping among which are synced } socket.reviseConnection(this.liveNodes); - let unavailableCount = this.nodesList.length - this.liveNodes.length; - let supportedCount = this.liveNodes.length - outOfSyncCount; + const unavailableCount = this.nodesList.length - this.liveNodes.length; + const supportedCount = this.liveNodes.length - outOfSyncCount; let nodesInfoString = ''; - if (unavailableCount) - nodesInfoString += `, ${unavailableCount} nodes didn't respond` - if (outOfSyncCount) - nodesInfoString += `, ${outOfSyncCount} nodes are not synced` + if (unavailableCount) { + nodesInfoString += `, ${unavailableCount} nodes didn't respond`; + } + if (outOfSyncCount) { + nodesInfoString += `, ${outOfSyncCount} nodes are not synced`; + } logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); - } - } catch (e) { logger.warn('[ADAMANT js-api] Health check: Error in checkNodes(), ' + e); } this.isCheckingNodes = false; - } async function getIP(url) { try { - let addresses = await dnsPromises.resolve4(url); - if (addresses && addresses[0] !== '0.0.0.0') - return addresses[0] + const addresses = await dnsPromises.resolve4(url); + if (addresses && addresses[0] !== '0.0.0.0') { + return addresses[0]; + } } catch (e) { } -}; +} /** * Requests status from a single ADAMANT node * @param url {string} Node URL to request - * @returns {Promise} Node's status information + * @return {Promise} Node's status information */ function checkNode(url) { return axios.get(url) - .then(function (response) { - return { status: response.data } - }) - .catch(function (error) { - return false - }) -}; + .then(function(response) { + return {status: response.data}; + }) + .catch(function(error) { + return false; + }); +} function unixTimestamp() { return new Date().getTime(); diff --git a/src/helpers/keys.js b/src/helpers/keys.js index e184a79..3d41fbf 100644 --- a/src/helpers/keys.js +++ b/src/helpers/keys.js @@ -1,39 +1,39 @@ -var sodium = require('sodium-browserify-tweetnacl'); -var crypto = require('crypto'); -var Mnemonic = require('bitcore-mnemonic'); -var bignum = require('./bignumber.js'); +const sodium = require('sodium-browserify-tweetnacl'); +const crypto = require('crypto'); +const Mnemonic = require('bitcore-mnemonic'); +const bignum = require('./bignumber.js'); module.exports = { - createNewPassPhrase: function () { + createNewPassPhrase: function() { return new Mnemonic(Mnemonic.Words.ENGLISH).toString(); }, - makeKeypairFromHash: function (hash) { - var keypair = sodium.crypto_sign_seed_keypair(hash); + makeKeypairFromHash: function(hash) { + const keypair = sodium.crypto_sign_seed_keypair(hash); return { publicKey: keypair.publicKey, - privateKey: keypair.secretKey + privateKey: keypair.secretKey, }; }, - createHashFromPassPhrase: function (passPhrase) { - var secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); + createHashFromPassPhrase: function(passPhrase) { + const secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); }, - createKeypairFromPassPhrase: function (passPhrase) { - var hash = this.createHashFromPassPhrase(passPhrase); + createKeypairFromPassPhrase: function(passPhrase) { + const hash = this.createHashFromPassPhrase(passPhrase); return this.makeKeypairFromHash(hash); }, - createAddressFromPublicKey: function (publicKey) { - var publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); - var temp = Buffer.alloc(8); - for (var i = 0; i < 8; i++) { + createAddressFromPublicKey: function(publicKey) { + const publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); + const temp = Buffer.alloc(8); + for (let i = 0; i < 8; i++) { temp[i] = publicKeyHash[7 - i]; } return 'U' + bignum.fromBuffer(temp).toString(); - } + }, -} +}; diff --git a/src/helpers/logger.js b/src/helpers/logger.js index 542f884..a215f2a 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -1,34 +1,40 @@ -let logger = { +const logger = { errorLevel: 'log', l: console, initLogger(errorLevel, log) { - if (errorLevel) + if (errorLevel) { this.errorLevel = errorLevel; - if (log) + } + if (log) { this.l = log; + } }, error(str) { - if (['error', 'warn', 'info', 'log'].includes(this.errorLevel)) + if (['error', 'warn', 'info', 'log'].includes(this.errorLevel)) { this.l.error(str); + } }, warn(str) { - if (['warn', 'info', 'log'].includes(this.errorLevel)) + if (['warn', 'info', 'log'].includes(this.errorLevel)) { this.l.warn(str); + } }, info(str) { - if (['info', 'log'].includes(this.errorLevel)) + if (['info', 'log'].includes(this.errorLevel)) { this.l.info(str); + } }, log(str) { - if (['log'].includes(this.errorLevel)) + if (['log'].includes(this.errorLevel)) { this.l.log(str); - } + } + }, }; diff --git a/src/helpers/time.js b/src/helpers/time.js index c57860e..c73b1a5 100644 --- a/src/helpers/time.js +++ b/src/helpers/time.js @@ -2,17 +2,17 @@ const constants = require('./constants.js'); module.exports = { - getEpochTime: function (time) { + getEpochTime: function(time) { if (time === undefined) { time = Date.now(); } - var d = constants.epochTime; - var t = d.getTime(); + const d = constants.epochTime; + const t = d.getTime(); return Math.floor((time - t) / 1000); }, - getTime: function (time) { + getTime: function(time) { return this.getEpochTime(time); - } + }, -} +}; diff --git a/src/helpers/transactionFormer.js b/src/helpers/transactionFormer.js index aa4c224..722b23e 100644 --- a/src/helpers/transactionFormer.js +++ b/src/helpers/transactionFormer.js @@ -1,14 +1,14 @@ -var sodium = require('sodium-browserify-tweetnacl'); -var crypto = require('crypto'); -var bignum = require('./bignumber.js'); -var keys = require('./keys.js'); -var ByteBuffer = require('bytebuffer'); +const sodium = require('sodium-browserify-tweetnacl'); +const crypto = require('crypto'); +const bignum = require('./bignumber.js'); +const keys = require('./keys.js'); +const ByteBuffer = require('bytebuffer'); const constants = require('./constants.js'); const time = require('./time.js'); module.exports = { - createTransaction: function (type, data) { + createTransaction: function(type, data) { switch (type) { case constants.transactionTypes.SEND: return this.createSendTransaction(data); @@ -24,14 +24,14 @@ module.exports = { return {}; }, - createBasicTransaction: function (data) { - var transaction = { type: data.transactionType, amount: 0, timestamp: time.getTime(), asset: {}, senderPublicKey: data.keyPair.publicKey.toString('hex'), senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey) }; + createBasicTransaction: function(data) { + const transaction = {type: data.transactionType, amount: 0, timestamp: time.getTime(), asset: {}, senderPublicKey: data.keyPair.publicKey.toString('hex'), senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey)}; return transaction; }, - createSendTransaction: function (data) { + createSendTransaction: function(data) { data.transactionType = constants.transactionTypes.SEND; - var transaction = this.createBasicTransaction(data); + const transaction = this.createBasicTransaction(data); transaction.asset = {}; transaction.recipientId = data.recipientId; transaction.amount = data.amount; @@ -39,15 +39,15 @@ module.exports = { return transaction; }, - createStateTransaction: function (data) { + createStateTransaction: function(data) { data.transactionType = constants.transactionTypes.STATE; - var transaction = this.createBasicTransaction(data); + const transaction = this.createBasicTransaction(data); transaction.asset = { - "state": { + 'state': { key: data.key, value: data.value, - type: 0 - } + type: 0, + }, }; transaction.recipientId = null; transaction.amount = 0; @@ -55,15 +55,15 @@ module.exports = { return transaction; }, - createChatTransaction: function (data) { + createChatTransaction: function(data) { data.transactionType = constants.transactionTypes.CHAT_MESSAGE; - var transaction = this.createBasicTransaction(data); + const transaction = this.createBasicTransaction(data); transaction.asset = { - "chat": { + 'chat': { message: data.message, own_message: data.own_message, - type: data.message_type - } + type: data.message_type, + }, }; transaction.recipientId = data.recipientId; transaction.amount = data.amount || 0; @@ -71,33 +71,33 @@ module.exports = { return transaction; }, - createDelegateTransaction: function (data) { + createDelegateTransaction: function(data) { data.transactionType = constants.transactionTypes.DELEGATE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { "delegate": { "username": data.username, publicKey: data.keyPair.publicKey.toString('hex') } }; + const transaction = this.createBasicTransaction(data); + transaction.asset = {'delegate': {'username': data.username, publicKey: data.keyPair.publicKey.toString('hex')}}; transaction.recipientId = null; transaction.signature = this.transactionSign(transaction, data.keyPair); return transaction; }, - createVoteTransaction: function (data) { + createVoteTransaction: function(data) { data.transactionType = constants.transactionTypes.VOTE; - var transaction = this.createBasicTransaction(data); - transaction.asset = { "votes": data.votes }; + const transaction = this.createBasicTransaction(data); + transaction.asset = {'votes': data.votes}; transaction.recipientId = transaction.senderId; transaction.signature = this.transactionSign(transaction, data.keyPair); return transaction; }, - getHash: function (trs) { + getHash: function(trs) { return crypto.createHash('sha256').update(this.getBytes(trs)).digest(); }, - getBytes: function (transaction) { - var skipSignature = false; - var skipSecondSignature = true; - var assetSize = 0; - var assetBytes = null; + getBytes: function(transaction) { + const skipSignature = false; + const skipSecondSignature = true; + let assetSize = 0; + let assetBytes = null; switch (transaction.type) { case constants.transactionTypes.SEND: @@ -123,18 +123,18 @@ module.exports = { return 0; } - var bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true); + const bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true); bb.writeByte(transaction.type); bb.writeInt(transaction.timestamp); - var senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, 'hex'); + const senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, 'hex'); for (var i = 0; i < senderPublicKeyBuffer.length; i++) { bb.writeByte(senderPublicKeyBuffer[i]); } if (transaction.requesterPublicKey) { - var requesterPublicKey = Buffer.from(transaction.requesterPublicKey, 'hex'); + const requesterPublicKey = Buffer.from(transaction.requesterPublicKey, 'hex'); for (var i = 0; i < requesterPublicKey.length; i++) { bb.writeByte(requesterPublicKey[i]); @@ -142,8 +142,8 @@ module.exports = { } if (transaction.recipientId) { - var recipient = transaction.recipientId.slice(1); - recipient = new bignum(recipient).toBuffer({ size: 8 }); + let recipient = transaction.recipientId.slice(1); + recipient = new bignum(recipient).toBuffer({size: 8}); for (i = 0; i < 8; i++) { bb.writeByte(recipient[i] || 0); @@ -163,22 +163,22 @@ module.exports = { } if (!skipSignature && transaction.signature) { - var signatureBuffer = Buffer.from(transaction.signature, 'hex'); + const signatureBuffer = Buffer.from(transaction.signature, 'hex'); for (var i = 0; i < signatureBuffer.length; i++) { bb.writeByte(signatureBuffer[i]); } } if (!skipSecondSignature && transaction.signSignature) { - var signSignatureBuffer = Buffer.from(transaction.signSignature, 'hex'); + const signSignatureBuffer = Buffer.from(transaction.signSignature, 'hex'); for (var i = 0; i < signSignatureBuffer.length; i++) { bb.writeByte(signSignatureBuffer[i]); } } bb.flip(); - var arrayBuffer = new Uint8Array(bb.toArrayBuffer()); - var buffer = []; + const arrayBuffer = new Uint8Array(bb.toArrayBuffer()); + const buffer = []; for (var i = 0; i < arrayBuffer.length; i++) { buffer[i] = arrayBuffer[i]; @@ -187,13 +187,13 @@ module.exports = { return Buffer.from(buffer); }, - transactionSign: function (trs, keypair) { - var hash = this.getHash(trs); + transactionSign: function(trs, keypair) { + const hash = this.getHash(trs); return this.sign(hash, keypair).toString('hex'); }, - voteGetBytes: function (trs) { - var buf; + voteGetBytes: function(trs) { + let buf; try { buf = trs.asset.votes ? Buffer.from(trs.asset.votes.join(''), 'utf8') : null; } catch (e) { @@ -202,11 +202,11 @@ module.exports = { return buf; }, - delegatesGetBytes: function (trs) { + delegatesGetBytes: function(trs) { if (!trs.asset.delegate.username) { return null; } - var buf; + let buf; try { buf = Buffer.from(trs.asset.delegate.username, 'utf8'); @@ -216,22 +216,22 @@ module.exports = { return buf; }, - statesGetBytes: function (trs) { + statesGetBytes: function(trs) { if (!trs.asset.state.value) { return null; } - var buf; + let buf; try { buf = Buffer.from([]); - var stateBuf = Buffer.from(trs.asset.state.value); + const stateBuf = Buffer.from(trs.asset.state.value); buf = Buffer.concat([buf, stateBuf]); if (trs.asset.state.key) { - var keyBuf = Buffer.from(trs.asset.state.key); + const keyBuf = Buffer.from(trs.asset.state.key); buf = Buffer.concat([buf, keyBuf]); } - var bb = new ByteBuffer(4 + 4, true); + const bb = new ByteBuffer(4 + 4, true); bb.writeInt(trs.asset.state.type); bb.flip(); @@ -243,19 +243,19 @@ module.exports = { return buf; }, - chatGetBytes: function (trs) { - var buf; + chatGetBytes: function(trs) { + let buf; try { buf = Buffer.from([]); - var messageBuf = Buffer.from(trs.asset.chat.message, 'hex'); + const messageBuf = Buffer.from(trs.asset.chat.message, 'hex'); buf = Buffer.concat([buf, messageBuf]); if (trs.asset.chat.own_message) { - var ownMessageBuf = Buffer.from(trs.asset.chat.own_message, 'hex'); + const ownMessageBuf = Buffer.from(trs.asset.chat.own_message, 'hex'); buf = Buffer.concat([buf, ownMessageBuf]); } - var bb = new ByteBuffer(4 + 4, true); + const bb = new ByteBuffer(4 + 4, true); bb.writeInt(trs.asset.chat.type); bb.flip(); buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); @@ -266,8 +266,8 @@ module.exports = { return buf; }, - sign: function (hash, keypair) { + sign: function(hash, keypair) { return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex')); - } + }, }; diff --git a/src/helpers/validator.js b/src/helpers/validator.js index a021e69..3be1940 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -1,55 +1,59 @@ const constants = require('./constants'); -const BigNumber = require('bignumber.js') +const BigNumber = require('bignumber.js'); module.exports = { getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1) + min); //The maximum is inclusive and the minimum is inclusive + return Math.floor(Math.random() * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive }, isNumeric(str) { - if (typeof str !== "string") return false - return !isNaN(str) && !isNaN(parseFloat(str)) + if (typeof str !== 'string') return false; + return !isNaN(str) && !isNaN(parseFloat(str)); }, tryParseJSON(jsonString) { try { - let o = JSON.parse(jsonString); - if (o && typeof o === "object") { + const o = JSON.parse(jsonString); + if (o && typeof o === 'object') { return o; } } catch (e) { } - return false + return false; }, validatePassPhrase(passPhrase) { - if (!passPhrase || typeof(passPhrase) !== 'string' || passPhrase.length < 30) - return false - else - return true + if (!passPhrase || typeof(passPhrase) !== 'string' || passPhrase.length < 30) { + return false; + } else { + return true; + } }, validateEndpoint(endpoint) { - if (!endpoint || typeof(endpoint) !== 'string') - return false - else - return true + if (!endpoint || typeof(endpoint) !== 'string') { + return false; + } else { + return true; + } }, validateAdmAddress(address) { - if (!address || typeof(address) !== 'string' || !constants.RE_ADM_ADDRESS.test(address)) - return false - else - return true + if (!address || typeof(address) !== 'string' || !constants.RE_ADM_ADDRESS.test(address)) { + return false; + } else { + return true; + } }, validateAdmPublicKey(publicKey) { - if (!publicKey || typeof(publicKey) !== 'string' || publicKey.length !== 64 || !constants.RE_HEX.test(publicKey)) - return false - else - return true + if (!publicKey || typeof(publicKey) !== 'string' || publicKey.length !== 64 || !constants.RE_HEX.test(publicKey)) { + return false; + } else { + return true; + } }, validateAdmVoteForPublicKey(publicKey) { @@ -65,61 +69,66 @@ module.exports = { }, validateIntegerAmount(amount) { - if (!amount || typeof(amount) !== 'number' || isNaN(amount) || !Number.isSafeInteger(amount)) - return false - else - return true + if (!amount || typeof(amount) !== 'number' || isNaN(amount) || !Number.isSafeInteger(amount)) { + return false; + } else { + return true; + } }, validateStringAmount(amount) { - if (!amount || !this.isNumeric(amount)) - return false - else - return true + if (!amount || !this.isNumeric(amount)) { + return false; + } else { + return true; + } }, validateMessageType(message_type) { - if (!message_type || typeof(message_type) !== 'number' || ![1,2,3].includes(message_type)) - return false - else - return true + if (!message_type || typeof(message_type) !== 'number' || ![1, 2, 3].includes(message_type)) { + return false; + } else { + return true; + } }, validateMessage(message, message_type) { - if (typeof(message) !== 'string') + if (typeof(message) !== 'string') { return { result: false, - error: `Message must be a string` - } - else { + error: `Message must be a string`, + }; + } else { if (message_type === 2 || message_type === 3) { + const json = this.tryParseJSON(message); - let json = this.tryParseJSON(message) - - if (!json) + if (!json) { return { result: false, - error: `For rich and signal messages, 'message' must be a JSON string` - } + error: `For rich and signal messages, 'message' must be a JSON string`, + }; + } - if (json.type && json.type.toLowerCase().includes('_transaction')) - if (json.type.toLowerCase() !== json.type) + if (json.type && json.type.toLowerCase().includes('_transaction')) { + if (json.type.toLowerCase() !== json.type) { return { result: false, - error: `Value '_transaction' must be in lower case` - } + error: `Value '_transaction' must be in lower case`, + }; + } + } - if (typeof json.amount !== 'string' || !this.validateStringAmount(json.amount)) + if (typeof json.amount !== 'string' || !this.validateStringAmount(json.amount)) { return { result: false, - error: `Field 'amount' must be a string, representing a number` - } - + error: `Field 'amount' must be a string, representing a number`, + }; + } } } return { - result: true - } + result: true, + }; }, validateDelegateName(name) { @@ -131,20 +140,20 @@ module.exports = { }, AdmToSats(amount) { - return BigNumber(String(amount)).multipliedBy(constants.SAT).integerValue().toNumber() + return BigNumber(String(amount)).multipliedBy(constants.SAT).integerValue().toNumber(); }, badParameter(name, value, customMessage) { return new Promise((resolve, reject) => { resolve({ success: false, - errorMessage: `Wrong '${name}' parameter${value ? ': ' + value : ''}${customMessage ? '. Error: ' + customMessage : ''}` - }) - }) + errorMessage: `Wrong '${name}' parameter${value ? ': ' + value : ''}${customMessage ? '. Error: ' + customMessage : ''}`, + }); + }); }, formatRequestResults(response, isRequestSuccess) { - let results = {}; + const results = {}; results.details = {}; if (isRequestSuccess) { @@ -153,8 +162,9 @@ module.exports = { results.details.status = response.status; results.details.statusText = response.statusText; results.details.response = response; - if (!results.success && results.data) - results.errorMessage = `Node's reply: ${results.data.error}` + if (!results.success && results.data) { + results.errorMessage = `Node's reply: ${results.data.error}`; + } } else { results.success = false; results.data = undefined; @@ -167,7 +177,6 @@ module.exports = { } return results; - - } + }, }; diff --git a/src/helpers/wsClient.js b/src/helpers/wsClient.js index 8895796..4070521 100644 --- a/src/helpers/wsClient.js +++ b/src/helpers/wsClient.js @@ -1,18 +1,16 @@ -const ioClient = require("socket.io-client"); +const ioClient = require('socket.io-client'); const logger = require('./logger'); const validator = require('./validator'); module.exports = { - isSocketEnabled: false, // If we need socket connection - wsType: "ws", // Socket connection type, "ws" (default) or "wss" + wsType: 'ws', // Socket connection type, 'ws' (default) or 'wss' admAddress: '', // ADM address to subscribe to notifications connection: null, // Socket connection onNewMessage: null, // Method to process new messages or transactions activeNodes: [], // List of nodes that are active. Not all of them synced and support socket. activeSocketNodes: [], // List of nodes that are active, synced and support socket useFastest: false, // If to connect to node with minimum ping. Not recommended. - // Constructor initSocket(params) { this.onNewMessage = params.onNewMessage; @@ -20,7 +18,6 @@ module.exports = { this.wsType = params.wsType; this.admAddress = params.admAddress; }, - // Runs after every healthCheck() to re-connect socket if needed reviseConnection(nodes) { if (!this.isSocketEnabled) { @@ -32,69 +29,63 @@ module.exports = { this.setConnection(); } }, - // Make socket connection and subscribe to new transactions setConnection() { - if (this.activeSocketNodes.length === 0) { - logger.warn(`[Socket] No supported socket nodes at the moment.`); - return; + if (!this.activeSocketNodes.length) { + return logger.warn(`[Socket] No supported socket nodes at the moment.`); } const node = this.socketAddress(); logger.log(`[Socket] Supported nodes: ${this.activeSocketNodes.length}. Connecting to ${node}...`); - this.connection = ioClient.connect(node, { reconnection: false, timeout: 5000 }); + this.connection = ioClient.connect(node, {reconnection: false, timeout: 5000}); this.connection.on('connect', () => { this.connection.emit('address', this.admAddress); logger.info('[Socket] Connected to ' + node + ' and subscribed to incoming transactions for ' + this.admAddress + '.'); }); - this.connection.on('disconnect', reason => { - logger.warn('[Socket] Disconnected. Reason: ' + reason) + this.connection.on('disconnect', (reason) => { + logger.warn('[Socket] Disconnected. Reason: ' + reason); }); this.connection.on('connect_error', (err) => { - logger.warn('[Socket] Connection error: ' + err) + logger.warn('[Socket] Connection error: ' + err); }); - this.connection.on('newTrans', transaction => { + this.connection.on('newTrans', (transaction) => { if ((transaction.recipientId === this.admAddress) && (transaction.type === 0 || transaction.type === 8)) { // console.info(`[Socket] New incoming socket transaction received: ${transaction.id}`); this.onNewMessage(transaction); } }); }, - // Save the list of nodes activeSocketNodes that are active, synced and support socket setNodes() { - this.activeSocketNodes = this.activeNodes.filter(n => n.socketSupport & !n.outOfSync); - // Remove nodes without IP if "ws" connection type - if (this.wsType === "ws") { - this.activeSocketNodes = this.activeSocketNodes.filter(n => !n.ifHttps || n.ip); + this.activeSocketNodes = this.activeNodes.filter((n) => n.socketSupport & !n.outOfSync); + // Remove nodes without IP if 'ws' connection type + if (this.wsType === 'ws') { + this.activeSocketNodes = this.activeSocketNodes.filter((n) => !n.ifHttps || n.ip); } }, - // Returns socket url for connection socketAddress() { const node = this.useFastest ? this.fastestNode() : this.randomNode(); - let socketUrl = this.wsType + "://"; - if (this.wsType === "ws") { + let socketUrl = this.wsType + '://'; + if (this.wsType === 'ws') { let host = node.ip; - if (!host || host === undefined) + if (!host || host === undefined) { host = node.url; - socketUrl = socketUrl + host + ":" + node.wsPort + } + socketUrl = socketUrl + host + ':' + node.wsPort; } else { socketUrl = socketUrl + node.url; // no port if wss } return socketUrl; }, - fastestNode() { return this.activeSocketNodes[0]; // They are sorted by ping }, - randomNode() { - return this.activeSocketNodes[validator.getRandomIntInclusive(0, this.activeSocketNodes.length - 1)] - } - -} + return this.activeSocketNodes[validator.getRandomIntInclusive(0, this.activeSocketNodes.length - 1)]; + }, +}; From 9255312b143e1789ac9fa46c1cdccf99b45492e0 Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 25 Mar 2022 19:40:16 +0600 Subject: [PATCH 11/31] refactor(validator): fix ESLint errors and simplify --- src/helpers/validator.js | 211 ++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 112 deletions(-) diff --git a/src/helpers/validator.js b/src/helpers/validator.js index 3be1940..cae86af 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -1,136 +1,112 @@ const constants = require('./constants'); -const BigNumber = require('bignumber.js'); +const bigNumber = require('bignumber.js'); module.exports = { + getRandomIntInclusive(minimum, maximum) { + const min = Math.ceil(minimum); + const max = Math.floor(maximum); - getRandomIntInclusive(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1) + min); // The maximum is inclusive and the minimum is inclusive + // The maximum is inclusive and the minimum is inclusive + return Math.floor(Math.random() * (max - min + 1) + min); }, - isNumeric(str) { - if (typeof str !== 'string') return false; - return !isNaN(str) && !isNaN(parseFloat(str)); - }, + if (typeof str !== 'string') { + return false; + } + return !isNaN(parseFloat(str)); + }, tryParseJSON(jsonString) { try { const o = JSON.parse(jsonString); - if (o && typeof o === 'object') { - return o; - } - } catch (e) { } - return false; - }, - validatePassPhrase(passPhrase) { - if (!passPhrase || typeof(passPhrase) !== 'string' || passPhrase.length < 30) { + return typeof o === 'object' ? o : false; + } catch (e) { return false; - } else { - return true; } }, - + validatePassPhrase(passPhrase) { + return typeof passPhrase === 'string' && passPhrase.length > 30; + }, validateEndpoint(endpoint) { - if (!endpoint || typeof(endpoint) !== 'string') { - return false; - } else { - return true; - } + return typeof endpoint === 'string'; }, - validateAdmAddress(address) { - if (!address || typeof(address) !== 'string' || !constants.RE_ADM_ADDRESS.test(address)) { - return false; - } else { - return true; - } + return typeof(address) === 'string' && constants.RE_ADM_ADDRESS.test(address); }, - validateAdmPublicKey(publicKey) { - if (!publicKey || typeof(publicKey) !== 'string' || publicKey.length !== 64 || !constants.RE_HEX.test(publicKey)) { - return false; - } else { - return true; - } + return ( + typeof publicKey === 'string' && + publicKey.length === 64 && + constants.RE_HEX.test(publicKey) + ); }, - validateAdmVoteForPublicKey(publicKey) { - return (publicKey && typeof(publicKey) === 'string' && constants.RE_ADM_VOTE_FOR_PUBLIC_KEY.test(publicKey)); + return ( + typeof publicKey === 'string' && + constants.RE_ADM_VOTE_FOR_PUBLIC_KEY.test(publicKey) + ); }, - validateAdmVoteForAddress(address) { - return (address && typeof(address) === 'string' && constants.RE_ADM_VOTE_FOR_ADDRESS.test(address)); + return ( + typeof address === 'string' && + constants.RE_ADM_VOTE_FOR_ADDRESS.test(address) + ); }, - validateAdmVoteForDelegateName(delegateName) { - return (delegateName && typeof(delegateName) === 'string' && constants.RE_ADM_VOTE_FOR_DELEGATE_NAME.test(delegateName)); + return ( + typeof delegateName === 'string' && + constants.RE_ADM_VOTE_FOR_DELEGATE_NAME.test(delegateName) + ); }, - validateIntegerAmount(amount) { - if (!amount || typeof(amount) !== 'number' || isNaN(amount) || !Number.isSafeInteger(amount)) { - return false; - } else { - return true; - } + return Number.isSafeInteger(amount); }, - validateStringAmount(amount) { - if (!amount || !this.isNumeric(amount)) { - return false; - } else { - return true; - } + return this.isNumeric(amount); }, - - validateMessageType(message_type) { - if (!message_type || typeof(message_type) !== 'number' || ![1, 2, 3].includes(message_type)) { - return false; - } else { - return true; - } + validateMessageType(messageType) { + return [1, 2, 3].includes(messageType); }, - - validateMessage(message, message_type) { - if (typeof(message) !== 'string') { + validateMessage(message, messageType) { + if (typeof message !== 'string') { return { result: false, error: `Message must be a string`, }; - } else { - if (message_type === 2 || message_type === 3) { - const json = this.tryParseJSON(message); + } - if (!json) { - return { - result: false, - error: `For rich and signal messages, 'message' must be a JSON string`, - }; - } + if ([2, 3].includes(messageType)) { + const json = this.tryParseJSON(message); - if (json.type && json.type.toLowerCase().includes('_transaction')) { - if (json.type.toLowerCase() !== json.type) { - return { - result: false, - error: `Value '_transaction' must be in lower case`, - }; - } - } + if (!json) { + return { + result: false, + error: `For rich and signal messages, 'message' must be a JSON string`, + }; + } - if (typeof json.amount !== 'string' || !this.validateStringAmount(json.amount)) { + if (json.type && json.type.toLowerCase().includes('_transaction')) { + if (json.type.toLowerCase() !== json.type) { return { result: false, - error: `Field 'amount' must be a string, representing a number`, + error: `Value '_transaction' must be in lower case`, }; } } + + if (typeof json.amount !== 'string' || !this.validateStringAmount(json.amount)) { + return { + result: false, + error: `Field 'amount' must be a string, representing a number`, + }; + } } + return { result: true, }; }, - validateDelegateName(name) { if (typeof name !== 'string') { return false; @@ -138,45 +114,56 @@ module.exports = { return constants.RE_ADM_DELEGATE_NAME.test(name); }, - AdmToSats(amount) { - return BigNumber(String(amount)).multipliedBy(constants.SAT).integerValue().toNumber(); + return bigNumber(String(amount)) + .multipliedBy(constants.SAT) + .integerValue() + .toNumber(); }, - - badParameter(name, value, customMessage) { - return new Promise((resolve, reject) => { - resolve({ - success: false, - errorMessage: `Wrong '${name}' parameter${value ? ': ' + value : ''}${customMessage ? '. Error: ' + customMessage : ''}`, - }); - }); + async badParameter(name, value, customMessage) { + return { + success: false, + errorMessage: `Wrong '${name}' parameter${value ? ': ' + value : ''}${customMessage ? '. Error: ' + customMessage : ''}`, + }; }, - formatRequestResults(response, isRequestSuccess) { - const results = {}; - results.details = {}; + let results = { + details: {}, + }; if (isRequestSuccess) { - results.success = response.data && response.data.success; - results.data = response.data; - results.details.status = response.status; - results.details.statusText = response.statusText; - results.details.response = response; + results = { + success: response.data && response.data.success, + data: response.data, + details: { + status: response.status, + statusText: response.statusText, + response: response, + }, + }; + if (!results.success && results.data) { results.errorMessage = `Node's reply: ${results.data.error}`; } } else { - results.success = false; - results.data = undefined; - results.details.status = response.response ? response.response.status : undefined; - results.details.statusText = response.response ? response.response.statusText : undefined; - results.details.error = response.toString(); - results.details.message = response.response && response.response.data ? response.response.data.toString().trim() : undefined; - results.details.response = response.response; - results.errorMessage = `${results.details.error}${results.details.message ? '. Message: ' + results.details.message : ''}`; + results = { + success: false, + data: undefined, + details: { + status: response.status, + statusText: response.response && response.response.statusText, + response: response.response && response.response.status, + error: response.toString(), + message: ( + response.response && + response.response.data && + response.response.data.toString().trim() + ), + errorMessage: `${results.details.error}${results.details.message ? '. Message: ' + results.details.message : ''}`, + }, + }; } return results; }, - }; From 186e80d92859d1ecfb8c3d9f4a131d0d2fb1b4ba Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 26 Mar 2022 19:25:07 +0600 Subject: [PATCH 12/31] refactor(transactionFormer): fix ESLint errors and make code cleaner --- src/helpers/transactionFormer.js | 384 ++++++++++++++++++------------- 1 file changed, 226 insertions(+), 158 deletions(-) diff --git a/src/helpers/transactionFormer.js b/src/helpers/transactionFormer.js index 722b23e..f8e9b91 100644 --- a/src/helpers/transactionFormer.js +++ b/src/helpers/transactionFormer.js @@ -1,127 +1,190 @@ const sodium = require('sodium-browserify-tweetnacl'); const crypto = require('crypto'); -const bignum = require('./bignumber.js'); -const keys = require('./keys.js'); const ByteBuffer = require('bytebuffer'); + +const BigNum = require('./bignumber.js'); +const keys = require('./keys.js'); const constants = require('./constants.js'); const time = require('./time.js'); module.exports = { + createTransaction(type, data) { + const { + SEND, + VOTE, + DELEGATE, + CHAT_MESSAGE, + STATE, + } = constants.transactionTypes; + + const actions = { + [SEND]: () => this.createSendTransaction(data), + [VOTE]: () => this.createVoteTransaction(data), + [DELEGATE]: () => this.createDelegateTransaction(data), + [CHAT_MESSAGE]: () => this.createChatTransaction(data), + [STATE]: () => this.createStateTransaction(data), + }; - createTransaction: function(type, data) { - switch (type) { - case constants.transactionTypes.SEND: - return this.createSendTransaction(data); - case constants.transactionTypes.VOTE: - return this.createVoteTransaction(data); - case constants.transactionTypes.DELEGATE: - return this.createDelegateTransaction(data); - case constants.transactionTypes.CHAT_MESSAGE: - return this.createChatTransaction(data); - case constants.transactionTypes.STATE: - return this.createStateTransaction(data); - } - return {}; + const action = actions[type]; + + return action ? action() : ({}); }, + createBasicTransaction(data) { + const transaction = { + type: data.transactionType, + timestamp: time.getTime(), + amount: 0, + senderPublicKey: data.keyPair.publicKey.toString('hex'), + senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey), + asset: {}, + }; - createBasicTransaction: function(data) { - const transaction = {type: data.transactionType, amount: 0, timestamp: time.getTime(), asset: {}, senderPublicKey: data.keyPair.publicKey.toString('hex'), senderId: keys.createAddressFromPublicKey(data.keyPair.publicKey)}; return transaction; }, + createSendTransaction(data) { + const details = { + ...data, + transactionType: constants.transactionTypes.SEND, + }; + + const transaction = { + ...this.createBasicTransaction(details), + recipientId: details.recipientId, + amount: details.amount, + asset: {}, + }; + + transaction.signature = this.transactionSign(transaction, details.keyPair); - createSendTransaction: function(data) { - data.transactionType = constants.transactionTypes.SEND; - const transaction = this.createBasicTransaction(data); - transaction.asset = {}; - transaction.recipientId = data.recipientId; - transaction.amount = data.amount; - transaction.signature = this.transactionSign(transaction, data.keyPair); return transaction; }, + createStateTransaction(data) { + const details = { + ...data, + transactionType: constants.transactionTypes.STATE, + }; - createStateTransaction: function(data) { - data.transactionType = constants.transactionTypes.STATE; - const transaction = this.createBasicTransaction(data); - transaction.asset = { - 'state': { - key: data.key, - value: data.value, - type: 0, + const transaction = { + ...this.createBasicTransaction(details), + recipientId: null, + amount: 0, + asset: { + state: { + key: details.key, + value: details.value, + type: 0, + }, }, }; - transaction.recipientId = null; - transaction.amount = 0; - transaction.signature = this.transactionSign(transaction, data.keyPair); + + transaction.signature = this.transactionSign(transaction, details.keyPair); + return transaction; }, + createChatTransaction(data) { + const details = { + ...data, + transactionType: constants.transactionTypes.CHAT_MESSAGE, + }; - createChatTransaction: function(data) { - data.transactionType = constants.transactionTypes.CHAT_MESSAGE; - const transaction = this.createBasicTransaction(data); - transaction.asset = { - 'chat': { - message: data.message, - own_message: data.own_message, - type: data.message_type, + const transaction = { + ...this.createBasicTransaction(details), + recipientId: details.recipientId, + amount: details.amount || 0, + asset: { + chat: { + message: data.message, + own_message: data.own_message, + type: data.message_type, + }, }, }; - transaction.recipientId = data.recipientId; - transaction.amount = data.amount || 0; - transaction.signature = this.transactionSign(transaction, data.keyPair); + + transaction.signature = this.transactionSign(transaction, details.keyPair); + return transaction; }, + createDelegateTransaction(data) { + const details = { + ...data, + transactionType: constants.transactionTypes.DELEGATE, + }; + + const transaction = { + ...this.createBasicTransaction(details), + recipientId: null, + asset: { + delegate: { + username: details.username, + publicKey: details.keyPair.publicKey.toString('hex'), + }, + }, + }; + + transaction.signature = this.transactionSign(transaction, details.keyPair); - createDelegateTransaction: function(data) { - data.transactionType = constants.transactionTypes.DELEGATE; - const transaction = this.createBasicTransaction(data); - transaction.asset = {'delegate': {'username': data.username, publicKey: data.keyPair.publicKey.toString('hex')}}; - transaction.recipientId = null; - transaction.signature = this.transactionSign(transaction, data.keyPair); return transaction; }, - createVoteTransaction: function(data) { - data.transactionType = constants.transactionTypes.VOTE; - const transaction = this.createBasicTransaction(data); - transaction.asset = {'votes': data.votes}; + const details = { + ...data, + transactionType: constants.transactionTypes.VOTE, + }; + + const transaction = { + ...this.createBasicTransaction(details), + asset: { + votes: details.votes, + }, + }; + transaction.recipientId = transaction.senderId; - transaction.signature = this.transactionSign(transaction, data.keyPair); + transaction.signature = this.transactionSign(transaction, details.keyPair); + return transaction; }, + getHash(trs) { + const hash = crypto + .createHash('sha256') + .update(this.getBytes(trs)) + .digest(); - getHash: function(trs) { - return crypto.createHash('sha256').update(this.getBytes(trs)).digest(); + return hash; }, + getAssetBytes(transaction) { + const {type} = transaction; + const { + SEND, + VOTE, + DELEGATE, + CHAT_MESSAGE, + STATE, + } = constants.transactionTypes; + + if (type === SEND) { + return {assetBytes: null, assetSize: 0}; + } + + const actions = { + [VOTE]: this.voteGetBytes, + [DELEGATE]: this.delegatesGetBytes, + [CHAT_MESSAGE]: this.chatGetBytes, + [STATE]: this.statesGetBytes, + }; + + const getBytes = actions[type]; - getBytes: function(transaction) { + if (getBytes) { + const assetBytes = getBytes(transaction); + + return {assetBytes, assetSize: assetBytes.length}; + } + }, + getBytes(transaction) { const skipSignature = false; const skipSecondSignature = true; - let assetSize = 0; - let assetBytes = null; - - switch (transaction.type) { - case constants.transactionTypes.SEND: - break; - case constants.transactionTypes.DELEGATE: - assetBytes = this.delegatesGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.STATE: - assetBytes = this.statesGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.VOTE: - assetBytes = this.voteGetBytes(transaction); - assetSize = assetBytes.length; - break; - case constants.transactionTypes.CHAT_MESSAGE: - assetBytes = this.chatGetBytes(transaction); - assetSize = assetBytes.length; - break; - default: - // 'Not supported yet' - return 0; - } + + const {assetSize, assetBytes} = this.getAssetBytes(transaction); const bb = new ByteBuffer(1 + 4 + 32 + 8 + 8 + 64 + 64 + assetSize, true); @@ -129,27 +192,29 @@ module.exports = { bb.writeInt(transaction.timestamp); const senderPublicKeyBuffer = Buffer.from(transaction.senderPublicKey, 'hex'); - for (var i = 0; i < senderPublicKeyBuffer.length; i++) { - bb.writeByte(senderPublicKeyBuffer[i]); + + for (const buf of senderPublicKeyBuffer) { + bb.writeByte(buf); } if (transaction.requesterPublicKey) { const requesterPublicKey = Buffer.from(transaction.requesterPublicKey, 'hex'); - for (var i = 0; i < requesterPublicKey.length; i++) { - bb.writeByte(requesterPublicKey[i]); + for (const buf of requesterPublicKey) { + bb.writeByte(buf); } } if (transaction.recipientId) { - let recipient = transaction.recipientId.slice(1); - recipient = new bignum(recipient).toBuffer({size: 8}); + const recipient = new BigNum( + transaction.recipientId.slice(1), + ).toBuffer({size: 8}); - for (i = 0; i < 8; i++) { + for (let i = 0; i < 8; i++) { bb.writeByte(recipient[i] || 0); } } else { - for (i = 0; i < 8; i++) { + for (let i = 0; i < 8; i++) { bb.writeByte(0); } } @@ -157,117 +222,120 @@ module.exports = { bb.writeLong(transaction.amount); if (assetSize > 0) { - for (var i = 0; i < assetSize; i++) { - bb.writeByte(assetBytes[i]); + for (const assetByte of assetBytes) { + bb.writeByte(assetByte); } } if (!skipSignature && transaction.signature) { const signatureBuffer = Buffer.from(transaction.signature, 'hex'); - for (var i = 0; i < signatureBuffer.length; i++) { - bb.writeByte(signatureBuffer[i]); + + for (const buf of signatureBuffer) { + bb.writeByte(buf); } } if (!skipSecondSignature && transaction.signSignature) { const signSignatureBuffer = Buffer.from(transaction.signSignature, 'hex'); - for (var i = 0; i < signSignatureBuffer.length; i++) { - bb.writeByte(signSignatureBuffer[i]); + + for (const buf of signSignatureBuffer) { + bb.writeByte(buf); } } bb.flip(); + const arrayBuffer = new Uint8Array(bb.toArrayBuffer()); const buffer = []; - for (var i = 0; i < arrayBuffer.length; i++) { + for (let i = 0; i < arrayBuffer.length; i++) { buffer[i] = arrayBuffer[i]; } return Buffer.from(buffer); }, - - transactionSign: function(trs, keypair) { + transactionSign(trs, keypair) { const hash = this.getHash(trs); + return this.sign(hash, keypair).toString('hex'); }, + voteGetBytes(trs) { + const {votes} = trs.asset; + + const buf = votes ? + Buffer.from(votes.join(''), 'utf8') : + null; - voteGetBytes: function(trs) { - let buf; - try { - buf = trs.asset.votes ? Buffer.from(trs.asset.votes.join(''), 'utf8') : null; - } catch (e) { - throw e; - } return buf; }, + delegatesGetBytes(trs) { + const {username} = trs.asset.delegate; - delegatesGetBytes: function(trs) { - if (!trs.asset.delegate.username) { - return null; - } - let buf; + const buf = username ? + Buffer.from(username, 'utf8') : + null; - try { - buf = Buffer.from(trs.asset.delegate.username, 'utf8'); - } catch (e) { - throw e; - } return buf; }, + statesGetBytes(trs) { + const {value} = trs.asset.state; - statesGetBytes: function(trs) { - if (!trs.asset.state.value) { + if (!value) { return null; } - let buf; - - try { - buf = Buffer.from([]); - const stateBuf = Buffer.from(trs.asset.state.value); - buf = Buffer.concat([buf, stateBuf]); - if (trs.asset.state.key) { - const keyBuf = Buffer.from(trs.asset.state.key); - buf = Buffer.concat([buf, keyBuf]); - } - const bb = new ByteBuffer(4 + 4, true); - bb.writeInt(trs.asset.state.type); - bb.flip(); + let buf = Buffer.from([]); - buf = Buffer.concat([buf, bb.toBuffer()]); - } catch (e) { - throw e; + const {key, type} = trs.asset.state; + + const stateBuf = Buffer.from(value); + + buf = Buffer.concat([buf, stateBuf]); + + if (key) { + const keyBuf = Buffer.from(key); + buf = Buffer.concat([buf, keyBuf]); } + const bb = new ByteBuffer(4 + 4, true); + + bb.writeInt(type); + bb.flip(); + + buf = Buffer.concat([buf, bb.toBuffer()]); + return buf; }, - chatGetBytes: function(trs) { - let buf; + let buf = Buffer.from([]); - try { - buf = Buffer.from([]); - const messageBuf = Buffer.from(trs.asset.chat.message, 'hex'); - buf = Buffer.concat([buf, messageBuf]); + const {message} = trs.asset.chat.message; + const messageBuf = Buffer.from(message, 'hex'); - if (trs.asset.chat.own_message) { - const ownMessageBuf = Buffer.from(trs.asset.chat.own_message, 'hex'); - buf = Buffer.concat([buf, ownMessageBuf]); - } - const bb = new ByteBuffer(4 + 4, true); - bb.writeInt(trs.asset.chat.type); - bb.flip(); - buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); - } catch (e) { - throw e; + buf = Buffer.concat([buf, messageBuf]); + + const {own_message: ownMessage} = trs.asset.chat; + + if (ownMessage) { + const ownMessageBuf = Buffer.from(ownMessage, 'hex'); + buf = Buffer.concat([buf, ownMessageBuf]); } + const bb = new ByteBuffer(4 + 4, true); + + bb.writeInt(trs.asset.chat.type); + bb.flip(); + + buf = Buffer.concat([buf, Buffer.from(bb.toBuffer())]); + return buf; }, + sign(hash, keypair) { + const sign = sodium.crypto_sign_detached( + hash, + Buffer.from(keypair.privateKey, 'hex'), + ); - sign: function(hash, keypair) { - return sodium.crypto_sign_detached(hash, Buffer.from(keypair.privateKey, 'hex')); + return sign; }, - }; From 0a07765478920ae1e78aeda91c67700e75a476d3 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 00:32:01 +0600 Subject: [PATCH 13/31] refactor(healthCheck): fix ESLint errors --- src/helpers/constants.js | 3 ++ src/helpers/healthCheck.js | 108 ++++++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 836dba0..9af3e15 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -40,4 +40,7 @@ module.exports = { RE_DOGE_ADDRESS: /^[A|D|9][A-Z0-9]([0-9a-zA-Z]{9,})$/, RE_LSK_ADDRESS: /^[0-9]{2,21}L$/, + RE_HTTP_URL: /^https?:\/\/(.*)$/, + RE_IP: /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/, + }; diff --git a/src/helpers/healthCheck.js b/src/helpers/healthCheck.js index 518c62f..2219f28 100644 --- a/src/helpers/healthCheck.js +++ b/src/helpers/healthCheck.js @@ -1,95 +1,100 @@ +const dnsPromises = require('dns').promises; + const axios = require('../helpers/axiosClient'); const socket = require('./wsClient'); const logger = require('./logger'); const validator = require('./validator'); -const dnsPromises = require('dns').promises; + +const {RE_IP, RE_HTTP_URL} = require('./constants'); const CHECK_NODES_INTERVAL = 60 * 5 * 1000; // Update active nodes every 5 minutes const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced module.exports = (nodes, checkHealthAtStartup = true) => { - isCheckingNodes = false; - nodesList = nodes; - activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data - liveNodes = []; + const isCheckingNodes = false; + const nodesList = nodes; + const activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data /** * Updates active nodes. If nodes are already updating, returns Promise of previous call + * @param {boolean} isPlannedUpdate * @return {Promise} Call changeNodes().then to do something when update complete */ - function changeNodes(isPlannedUpdate = false) { + async function changeNodes(isPlannedUpdate = false) { if (!isCheckingNodes) { - changeNodesPromise = new Promise(async (resolve) => { - if (!isPlannedUpdate) { - logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); - } - await checkNodes(isPlannedUpdate ? false : true); - resolve(true); - }); + if (!isPlannedUpdate) { + logger.warn('[ADAMANT js-api] Health check: Forcing to update active nodes…'); + } + + await checkNodes(!isPlannedUpdate); + + return true; } - return changeNodesPromise; } - if (checkHealthAtStartup) { changeNodes(true); - setInterval(() => { - changeNodes(true); - }, CHECK_NODES_INTERVAL); + + setInterval( + () => changeNodes(true), + CHECK_NODES_INTERVAL, + ); } return { - /** * @return {string} Current active node, f. e. http://88.198.156.44:36666 */ - node: () => { - return activeNode; - }, - + node: () => activeNode, changeNodes, - }; }; /** * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active + * @param {boolean} forceChangeActiveNode */ async function checkNodes(forceChangeActiveNode) { this.isCheckingNodes = true; this.liveNodes = []; try { - for (const n of this.nodesList) { + for (const node of this.nodesList) { try { const start = unixTimestamp(); - const req = await checkNode(n + '/api/node/status'); - const url = n.replace(/^https?:\/\/(.*)$/, '$1').split(':')[0]; - const ifIP = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/.test(url); + + const req = await checkNode(`${node}/api/node/status`); + + const [url] = node.replace(RE_HTTP_URL, '$1').split(':'); + const ifIP = RE_IP.test(url); + + const ip = ifIP ? url : await getIP(url); + const ifHttps = node.startsWith('https'); if (req.status) { this.liveNodes.push({ - node: n, - ifHttps: n.startsWith('https'), + node, ifIP, url, + ip, + ifHttps, outOfSync: false, ping: unixTimestamp() - start, height: req.status.network.height, heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), - ip: ifIP ? url : await getIP(url), - socketSupport: req.status.wsClient && req.status.wsClient.enabled, - wsPort: req.status.wsClient.port, + socketSupport: req.status.wsClient?.enabled, + wsPort: req.status.wsClient?.port, }); } else { - logger.log(`[ADAMANT js-api] Health check: Node ${n} haven't returned its status`); + logger.log(`[ADAMANT js-api] Health check: Node ${node} haven't returned its status`); } } catch (e) { - logger.log(`[ADAMANT js-api] Health check: Error while checking node ${n}, ` + e); + logger.log(`[ADAMANT js-api] Health check: Error while checking node ${node}, ${e}`); } } const count = this.liveNodes.length; + let outOfSyncCount = 0; if (!count) { @@ -99,9 +104,10 @@ async function checkNodes(forceChangeActiveNode) { if (count === 1) { this.activeNode = this.liveNodes[0].node; } else if (count === 2) { - const h0 = this.liveNodes[0]; - const h1 = this.liveNodes[1]; + const [h0, h1] = this.liveNodes; + this.activeNode = h0.height > h1.height ? h0.node : h1.node; + // Mark node outOfSync if needed if (h0.heightEpsilon > h1.heightEpsilon) { this.liveNodes[1].outOfSync = true; @@ -113,12 +119,15 @@ async function checkNodes(forceChangeActiveNode) { } else { let biggestGroup = []; // Removing lodash: const groups = _.groupBy(this.liveNodes, n => n.heightEpsilon); - const groups = this.liveNodes.reduce(function(grouped, node) { + const groups = this.liveNodes.reduce((grouped, node) => { const int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded - if (!grouped.hasOwnProperty(int)) { + + if (!Object.prototype.hasOwnProperty.call(grouped, int)) { grouped[int] = []; } + grouped[int].push(node); + return grouped; }, {}); @@ -132,28 +141,36 @@ async function checkNodes(forceChangeActiveNode) { this.liveNodes.forEach((node) => { node.outOfSync = !biggestGroup.includes(node); }); + outOfSyncCount = this.liveNodes.length - biggestGroup.length; biggestGroup.sort((a, b) => a.ping - b.ping); this.liveNodes.sort((a, b) => a.ping - b.ping); if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) { + // Use random node from which are synced this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node; - } // Use random node from which are synced - else { + } else { + // Use node with minimum ping among which are synced this.activeNode = biggestGroup[0].node; - } // Use node with minimum ping among which are synced + } } + socket.reviseConnection(this.liveNodes); + const unavailableCount = this.nodesList.length - this.liveNodes.length; const supportedCount = this.liveNodes.length - outOfSyncCount; + let nodesInfoString = ''; + if (unavailableCount) { nodesInfoString += `, ${unavailableCount} nodes didn't respond`; } + if (outOfSyncCount) { nodesInfoString += `, ${outOfSyncCount} nodes are not synced`; } + logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); } } catch (e) { @@ -166,15 +183,18 @@ async function checkNodes(forceChangeActiveNode) { async function getIP(url) { try { const addresses = await dnsPromises.resolve4(url); + if (addresses && addresses[0] !== '0.0.0.0') { return addresses[0]; } - } catch (e) { } + } catch (error) { + return; + } } /** * Requests status from a single ADAMANT node - * @param url {string} Node URL to request + * @param {string} url Node URL to request * @return {Promise} Node's status information */ function checkNode(url) { From 094ff332509cda4dac5d2dc3a3a6f7e3dd3c9499 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 01:54:07 +0600 Subject: [PATCH 14/31] refactor(groups, helpers): fix ESLint errors, JSDoc and request retries --- src/groups/newDelegate.js | 2 +- src/groups/voteForDelegate.js | 3 ++- src/helpers/bignumber.js | 24 ++++++++++++++---------- src/helpers/encryptor.js | 29 +++++++++++++++++++---------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/groups/newDelegate.js b/src/groups/newDelegate.js index 8b802da..d2bd7d3 100644 --- a/src/groups/newDelegate.js +++ b/src/groups/newDelegate.js @@ -58,7 +58,7 @@ module.exports = (nodeManager) => { return nodeManager.changeNodes() .then(() => ( - module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) + module.exports(nodeManager)(passPhrase, username, maxRetries, ++retryNo) )); } diff --git a/src/groups/voteForDelegate.js b/src/groups/voteForDelegate.js index f0d247d..5ef15c0 100644 --- a/src/groups/voteForDelegate.js +++ b/src/groups/voteForDelegate.js @@ -18,6 +18,7 @@ module.exports = (nodeManager) => { * @param {string[]} votes PublicKeys, ADM addresses and delegate names for upvote and downvote. * It would be more efficient to pass publicKey, otherwise the api will make additional queries * @param {number} maxRetries How much times to retry request + * @param {number} retryNo Number of request already made * @return {Promise} Request results */ return async (passPhrase, votes, maxRetries = DEFAULT_VOTE_FOR_DELEGATE_RETRIES, retryNo = 0) => { @@ -108,7 +109,7 @@ module.exports = (nodeManager) => { return nodeManager.changeNodes() .then(() => ( - module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) + module.exports(nodeManager)(passPhrase, votes, maxRetries, ++retryNo) )); } diff --git a/src/helpers/bignumber.js b/src/helpers/bignumber.js index cbd6c81..3d03e46 100644 --- a/src/helpers/bignumber.js +++ b/src/helpers/bignumber.js @@ -54,11 +54,15 @@ BigNumber.fromBuffer = function(buf, opts) { */ BigNumber.prototype.toBuffer = function(opts) { if (typeof opts === 'string') { - if (opts !== 'mpint') return 'Unsupported Buffer representation'; + if (opts !== 'mpint') { + return 'Unsupported Buffer representation'; + } const abs = this.abs(); - var buf = abs.toBuffer({size: 1, endian: 'big'}); - var len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length; + const buf = abs.toBuffer({size: 1, endian: 'big'}); + + let len = buf.length === 1 && buf[0] === 0 ? 0 : buf.length; + if (buf[0] & 0x80) len++; const ret = Buffer.alloc(4 + len); @@ -83,7 +87,9 @@ BigNumber.prototype.toBuffer = function(opts) { return ret; } - if (!opts) opts = {}; + if (!opts) { + opts = {}; + } const endian = {1: 'big', '-1': 'little'}[opts.endian] || opts.endian || 'big'; @@ -96,19 +102,17 @@ BigNumber.prototype.toBuffer = function(opts) { const size = opts.size === 'auto' ? Math.ceil(hex.length / 2) : (opts.size || 1); - var len = Math.ceil(hex.length / (2 * size)) * size; - var buf = Buffer.alloc(len); + const len = Math.ceil(hex.length / (2 * size)) * size; + const buf = Buffer.alloc(len); // Zero-pad the hex string so the chunks are all `size` long while (hex.length < 2 * len) hex = '0' + hex; const hx = hex .split(new RegExp('(.{' + (2 * size) + '})')) - .filter(function(s) { - return s.length > 0; - }); + .filter((s) => s.length > 0); - hx.forEach(function(chunk, i) { + hx.forEach((chunk, i) => { for (let j = 0; j < size; j++) { const ix = i * size + (endian === 'big' ? j : size - j - 1); buf[ix] = parseInt(chunk.slice(j * 2, j * 2 + 2), 16); diff --git a/src/helpers/encryptor.js b/src/helpers/encryptor.js index 981c3b0..ab983f0 100644 --- a/src/helpers/encryptor.js +++ b/src/helpers/encryptor.js @@ -3,33 +3,42 @@ const nacl = require('tweetnacl/nacl-fast'); const ed2curve = require('ed2curve'); module.exports = { + bytesToHex(bytes) { + const hex = []; - bytesToHex: function(bytes) { - for (var hex = [], i = 0; i < bytes.length; i++) { - hex.push((bytes[i] >>> 4).toString(16)); - hex.push((bytes[i] & 0xF).toString(16)); + for (let i = 0; i < bytes.length; i++) { + hex.push( + (bytes[i] >>> 4).toString(16), + (bytes[i] & 0xF).toString(16), + ); } + return hex.join(''); }, + hexToBytes(hex) { + const bytes = []; - hexToBytes: function(hex) { - for (var bytes = [], c = 0; c < hex.length; c += 2) { + for (let c = 0; c < hex.length; c += 2) { bytes.push(parseInt(hex.substr(c, 2), 16)); } + return bytes; }, - - encodeMessage: function(msg, keypair, recipientPublicKey) { + encodeMessage(msg, keypair, recipientPublicKey) { const nonce = Buffer.allocUnsafe(24); sodium.randombytes(nonce); + const plainText = Buffer.from(msg.toString()); - const DHPublicKey = ed2curve.convertPublicKey(new Uint8Array(this.hexToBytes(recipientPublicKey))); const DHSecretKey = ed2curve.convertSecretKey(keypair.privateKey); + const DHPublicKey = ed2curve.convertPublicKey( + new Uint8Array(this.hexToBytes(recipientPublicKey)), + ); + const encrypted = nacl.box(plainText, nonce, DHPublicKey, DHSecretKey); + return { message: this.bytesToHex(encrypted), own_message: this.bytesToHex(nonce), }; }, - }; From b7ae81d26df20ac513ec86c9df9585344ab2fc3b Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 02:04:27 +0600 Subject: [PATCH 15/31] refactor(groups, helpers): fix ESLint errors, JSDoc --- src/groups/coinNetworks.js | 2 +- src/groups/decodeMsg.js | 15 ++++++------ src/groups/newDelegate.js | 1 + src/groups/sendMessage.js | 48 +++++++++++++++++++++----------------- src/groups/sendTokens.js | 11 +++++---- src/helpers/validator.js | 2 +- 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/groups/coinNetworks.js b/src/groups/coinNetworks.js index a70f64e..d3bc749 100644 --- a/src/groups/coinNetworks.js +++ b/src/groups/coinNetworks.js @@ -1,4 +1,4 @@ -coininfo = require('coininfo'); +const coininfo = require('coininfo'); module.exports = { DOGE: coininfo.dogecoin.main.toBitcoinJS(), diff --git a/src/groups/decodeMsg.js b/src/groups/decodeMsg.js index da25f36..ddc6654 100644 --- a/src/groups/decodeMsg.js +++ b/src/groups/decodeMsg.js @@ -22,7 +22,7 @@ module.exports = (msg, senderPublicKey, passPhrase, nonce) => { const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey); const DHSecretKey = ed2curve.convertSecretKey(privateKey); const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey); - return decrypted ? Utf8ArrayToStr(decrypted) : ''; + return decrypted ? utf8ArrayToStr(decrypted) : ''; }; function hexToBytes(hexString = '') { @@ -35,13 +35,14 @@ function hexToBytes(hexString = '') { return Uint8Array.from(bytes); } -function Utf8ArrayToStr(array) { - let out; let i; let len; let c; - let char2; let char3; +function utf8ArrayToStr(array) { + const len = array.length; + let out = ''; + let i = 0; + let c; + let char2; + let char3; - out = ''; - len = array.length; - i = 0; while (i < len) { c = array[i++]; switch (c >> 4) { diff --git a/src/groups/newDelegate.js b/src/groups/newDelegate.js index d2bd7d3..48088b2 100644 --- a/src/groups/newDelegate.js +++ b/src/groups/newDelegate.js @@ -15,6 +15,7 @@ module.exports = (nodeManager) => { * It must be unique in ADAMANT blockchain. It should not be similar to ADAMANT address. * Delegate name can only contain alphanumeric characters and symbols !@$&_. * @param {number} maxRetries How much times to retry request + * @param {number} retryNo Number of request already made * @return {Promise} Request results */ return async (passPhrase, username, maxRetries = DEFAULT_NEW_DELEGATE_RETRIES, retryNo = 0) => { diff --git a/src/groups/sendMessage.js b/src/groups/sendMessage.js index 04f7344..412d1df 100644 --- a/src/groups/sendMessage.js +++ b/src/groups/sendMessage.js @@ -18,16 +18,20 @@ module.exports = (nodeManager) => { * Using public key is faster, as the library wouldn't request it from the network. * Though we cache public keys, and next request with address will be processed as fast as with public key. * @param {string} message Message plain text in case of basic message. Stringified JSON in case of rich or signal messages. The library will encrypt a message. - * Example of rich message for Ether in-chat transfer: `{"type":"eth_transaction","amount":"0.002","hash":"0xfa46d2b3c99878f1f9863fcbdb0bc27d220d7065c6528543cbb83ced84487deb","comments":"I like to send it, send it"}` - * @param {string, number} message_type Type of message: basic, rich, or signal - * @param {string, number} amount Amount to send with a message + * Example of rich message for Ether in-chat transfer: + * `{"type":"eth_transaction","amount":"0.002","hash":"0xfa46d2b3c99878f1f9863fcbdb0bc27d220d7065c6528543cbb83ced84487deb","comments":"I like to send it, send it"}` + * @param {string | number} messageType Type of message: basic, rich, or signal + * @param {string | number} amount Amount to send with a message * @param {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) * @param {number} maxRetries How much times to retry request - * @returns {Promise} Request results + * @param {number} retryNo Number of request already made + * @return {Promise} Request results */ - return async (passPhrase, addressOrPublicKey, message, message_type = 'basic', amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_MESSAGE_RETRIES, retryNo = 0) => { - let keyPair; let data; - let address; let publicKey; + return async (passPhrase, addressOrPublicKey, message, messageType = 'basic', amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_MESSAGE_RETRIES, retryNo = 0) => { + let keyPair; + let data; + let address; + let publicKey; try { if (!validator.validatePassPhrase(passPhrase)) { @@ -52,21 +56,21 @@ module.exports = (nodeManager) => { address = addressOrPublicKey; } - if (message_type === 'basic') { - message_type = 1; + if (messageType === 'basic') { + messageType = 1; } - if (message_type === 'rich') { - message_type = 2; + if (messageType === 'rich') { + messageType = 2; } - if (message_type === 'signal') { - message_type = 3; + if (messageType === 'signal') { + messageType = 3; } - if (!validator.validateMessageType(message_type)) { - return validator.badParameter('message_type', message_type); + if (!validator.validateMessageType(messageType)) { + return validator.badParameter('messageType', messageType); } - const messageValidation = validator.validateMessage(message, message_type); + const messageValidation = validator.validateMessage(message, messageType); if (!messageValidation.result) { return validator.badParameter('message', message, messageValidation.error); } @@ -74,18 +78,20 @@ module.exports = (nodeManager) => { data = { keyPair, recipientId: address, - message_type, + messageType, }; if (amount) { + let amountInSat = amount; + if (isAmountInADM) { - amountInSat = validator.AdmToSats(amount); - } else { - amountInSat = amount; + amountInSat = validator.admToSats(amount); } + if (!validator.validateIntegerAmount(amountInSat)) { return validator.badParameter('amount', amount); } + data.amount = amountInSat; } } catch (e) { @@ -123,7 +129,7 @@ module.exports = (nodeManager) => { logger.log(`${logMessage} Retrying…`); return nodeManager.changeNodes() .then(function() { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, message, message_type, tokensAmount, maxRetries, ++retryNo); + return module.exports(nodeManager)(passPhrase, addressOrPublicKey, message, messageType, amount, isAmountInADM, maxRetries, ++retryNo); }); } logger.warn(`${logMessage} No more attempts, returning error.`); diff --git a/src/groups/sendTokens.js b/src/groups/sendTokens.js index bd35d78..11d5fce 100644 --- a/src/groups/sendTokens.js +++ b/src/groups/sendTokens.js @@ -14,10 +14,11 @@ module.exports = (nodeManager) => { * @param {string} passPhrase Senders's passPhrase. Sender's address will be derived from it. * @param {string} addressOrPublicKey Recipient's ADAMANT address or public key. * Address is preferred, as if we get public key, we should derive address from it. - * @param {string, number} amount Amount to send + * @param {string | number} amount Amount to send * @param {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) * @param {number} maxRetries How much times to retry request - * @returns {Promise} Request results + * @param {number} retryNo Number of request already made + * @return {Promise} Request results */ return (passPhrase, addressOrPublicKey, amount, isAmountInADM = true, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { let transaction; @@ -46,10 +47,10 @@ module.exports = (nodeManager) => { address = addressOrPublicKey; } + let amountInSat = amount; + if (isAmountInADM) { - amountInSat = validator.AdmToSats(amount); - } else { - amountInSat = amount; + amountInSat = validator.admToSats(amount); } if (!validator.validateIntegerAmount(amountInSat)) { diff --git a/src/helpers/validator.js b/src/helpers/validator.js index cae86af..179ed6c 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -114,7 +114,7 @@ module.exports = { return constants.RE_ADM_DELEGATE_NAME.test(name); }, - AdmToSats(amount) { + admToSats(amount) { return bigNumber(String(amount)) .multipliedBy(constants.SAT) .integerValue() From 7edf889a4ed470ecb3cab209296533fc3aaab83e Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 02:17:27 +0600 Subject: [PATCH 16/31] fix(sendMessage): fix `message_type` name property --- src/groups/sendMessage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/groups/sendMessage.js b/src/groups/sendMessage.js index 412d1df..5c447b6 100644 --- a/src/groups/sendMessage.js +++ b/src/groups/sendMessage.js @@ -78,7 +78,7 @@ module.exports = (nodeManager) => { data = { keyPair, recipientId: address, - messageType, + message_type: messageType, }; if (amount) { From 8a842728665cef81388edbcbc1a539324d735534 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 02:23:35 +0600 Subject: [PATCH 17/31] refactor(sendMessage): simpify and remove useless code --- src/groups/sendMessage.js | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/groups/sendMessage.js b/src/groups/sendMessage.js index 5c447b6..e402f34 100644 --- a/src/groups/sendMessage.js +++ b/src/groups/sendMessage.js @@ -56,21 +56,20 @@ module.exports = (nodeManager) => { address = addressOrPublicKey; } - if (messageType === 'basic') { - messageType = 1; - } - if (messageType === 'rich') { - messageType = 2; - } - if (messageType === 'signal') { - messageType = 3; - } + const messageTypes = { + basic: 1, + rich: 2, + signal: 3, + }; + + messageType = messageTypes[messageType]; if (!validator.validateMessageType(messageType)) { return validator.badParameter('messageType', messageType); } const messageValidation = validator.validateMessage(message, messageType); + if (!messageValidation.result) { return validator.badParameter('message', message, messageValidation.error); } @@ -103,12 +102,10 @@ module.exports = (nodeManager) => { } if (!publicKey) { - return new Promise((resolve, reject) => { - resolve({ - success: false, - errorMessage: `Unable to get public key for ${addressOrPublicKey}. It is necessary for sending an encrypted message. Account may be uninitialized (https://medium.com/adamant-im/chats-and-uninitialized-accounts-in-adamant-5035438e2fcd), or network error`, - }); - }); + return { + success: false, + errorMessage: `Unable to get public key for ${addressOrPublicKey}. It is necessary for sending an encrypted message. Account may be uninitialized (https://medium.com/adamant-im/chats-and-uninitialized-accounts-in-adamant-5035438e2fcd), or network error`, + }; } try { @@ -136,12 +133,10 @@ module.exports = (nodeManager) => { return validator.formatRequestResults(error, false); }); } catch (e) { - return new Promise((resolve, reject) => { - resolve({ - success: false, - errorMessage: `Unable to encode message '${message}' with public key ${publicKey}, or unable to build a transaction. Exception: ` + e, - }); - }); + return { + success: false, + errorMessage: `Unable to encode message '${message}' with public key ${publicKey}, or unable to build a transaction. Exception: ` + e, + }; } }; // sendMessage() }; From 629b92ed5600812f813fb79ea05dbcca099fd68a Mon Sep 17 00:00:00 2001 From: martiliones Date: Sun, 27 Mar 2022 02:39:49 +0600 Subject: [PATCH 18/31] fix(healthCheck): variables activeNode and isCheckingNodes do not update --- src/helpers/healthCheck.js | 257 ++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 130 deletions(-) diff --git a/src/helpers/healthCheck.js b/src/helpers/healthCheck.js index 2219f28..fb97678 100644 --- a/src/helpers/healthCheck.js +++ b/src/helpers/healthCheck.js @@ -11,9 +11,10 @@ const CHECK_NODES_INTERVAL = 60 * 5 * 1000; // Update active nodes every 5 minut const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced module.exports = (nodes, checkHealthAtStartup = true) => { - const isCheckingNodes = false; const nodesList = nodes; - const activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data + let isCheckingNodes = false; + let liveNodes = []; + let activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data /** * Updates active nodes. If nodes are already updating, returns Promise of previous call @@ -32,153 +33,153 @@ module.exports = (nodes, checkHealthAtStartup = true) => { } } - if (checkHealthAtStartup) { - changeNodes(true); + /** + * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active + * @param {boolean} forceChangeActiveNode + */ + async function checkNodes(forceChangeActiveNode) { + isCheckingNodes = true; + liveNodes = []; + + try { + for (const node of nodesList) { + try { + const start = unixTimestamp(); + + const req = await checkNode(`${node}/api/node/status`); + + const [url] = node.replace(RE_HTTP_URL, '$1').split(':'); + const ifIP = RE_IP.test(url); + + const ip = ifIP ? url : await getIP(url); + const ifHttps = node.startsWith('https'); + + if (req.status) { + liveNodes.push({ + node, + ifIP, + url, + ip, + ifHttps, + outOfSync: false, + ping: unixTimestamp() - start, + height: req.status.network.height, + heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), + socketSupport: req.status.wsClient?.enabled, + wsPort: req.status.wsClient?.port, + }); + } else { + logger.log(`[ADAMANT js-api] Health check: Node ${node} haven't returned its status`); + } + } catch (e) { + logger.log(`[ADAMANT js-api] Health check: Error while checking node ${node}, ${e}`); + } + } - setInterval( - () => changeNodes(true), - CHECK_NODES_INTERVAL, - ); - } + const count = liveNodes.length; - return { - /** - * @return {string} Current active node, f. e. http://88.198.156.44:36666 - */ - node: () => activeNode, - changeNodes, - }; -}; + let outOfSyncCount = 0; -/** - * Requests every ADAMANT node for its status, makes a list of live nodes, and chooses one active - * @param {boolean} forceChangeActiveNode - */ -async function checkNodes(forceChangeActiveNode) { - this.isCheckingNodes = true; - this.liveNodes = []; - - try { - for (const node of this.nodesList) { - try { - const start = unixTimestamp(); - - const req = await checkNode(`${node}/api/node/status`); - - const [url] = node.replace(RE_HTTP_URL, '$1').split(':'); - const ifIP = RE_IP.test(url); - - const ip = ifIP ? url : await getIP(url); - const ifHttps = node.startsWith('https'); - - if (req.status) { - this.liveNodes.push({ - node, - ifIP, - url, - ip, - ifHttps, - outOfSync: false, - ping: unixTimestamp() - start, - height: req.status.network.height, - heightEpsilon: Math.round(req.status.network.height / HEIGHT_EPSILON), - socketSupport: req.status.wsClient?.enabled, - wsPort: req.status.wsClient?.port, - }); + if (!count) { + logger.error(`[ADAMANT js-api] Health check: All of ${nodesList.length} nodes are unavailable. Check internet connection and nodes list in config.`); + } else { + // Set activeNode to one that have maximum height and minimum ping + if (count === 1) { + activeNode = liveNodes[0].node; + } else if (count === 2) { + const [h0, h1] = liveNodes; + + activeNode = h0.height > h1.height ? h0.node : h1.node; + + // Mark node outOfSync if needed + if (h0.heightEpsilon > h1.heightEpsilon) { + liveNodes[1].outOfSync = true; + outOfSyncCount += 1; + } else if (h0.heightEpsilon < h1.heightEpsilon) { + liveNodes[0].outOfSync = true; + outOfSyncCount += 1; + } } else { - logger.log(`[ADAMANT js-api] Health check: Node ${node} haven't returned its status`); - } - } catch (e) { - logger.log(`[ADAMANT js-api] Health check: Error while checking node ${node}, ${e}`); - } - } - - const count = this.liveNodes.length; + let biggestGroup = []; + // Removing lodash: const groups = _.groupBy(liveNodes, n => n.heightEpsilon); + const groups = liveNodes.reduce((grouped, node) => { + const int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded - let outOfSyncCount = 0; + if (!Object.prototype.hasOwnProperty.call(grouped, int)) { + grouped[int] = []; + } - if (!count) { - logger.error(`[ADAMANT js-api] Health check: All of ${this.nodesList.length} nodes are unavailable. Check internet connection and nodes list in config.`); - } else { - // Set activeNode to one that have maximum height and minimum ping - if (count === 1) { - this.activeNode = this.liveNodes[0].node; - } else if (count === 2) { - const [h0, h1] = this.liveNodes; + grouped[int].push(node); - this.activeNode = h0.height > h1.height ? h0.node : h1.node; + return grouped; + }, {}); - // Mark node outOfSync if needed - if (h0.heightEpsilon > h1.heightEpsilon) { - this.liveNodes[1].outOfSync = true; - outOfSyncCount += 1; - } else if (h0.heightEpsilon < h1.heightEpsilon) { - this.liveNodes[0].outOfSync = true; - outOfSyncCount += 1; - } - } else { - let biggestGroup = []; - // Removing lodash: const groups = _.groupBy(this.liveNodes, n => n.heightEpsilon); - const groups = this.liveNodes.reduce((grouped, node) => { - const int = Math.floor(node.heightEpsilon); // Excessive, it is already rounded + Object.keys(groups).forEach((key) => { + if (groups[key].length > biggestGroup.length) { + biggestGroup = groups[key]; + } + }); - if (!Object.prototype.hasOwnProperty.call(grouped, int)) { - grouped[int] = []; - } + // All the nodes from the biggestGroup list are considered to be in sync, all the others are not + liveNodes.forEach((node) => { + node.outOfSync = !biggestGroup.includes(node); + }); - grouped[int].push(node); + outOfSyncCount = liveNodes.length - biggestGroup.length; - return grouped; - }, {}); + biggestGroup.sort((a, b) => a.ping - b.ping); + liveNodes.sort((a, b) => a.ping - b.ping); - Object.keys(groups).forEach((key) => { - if (groups[key].length > biggestGroup.length) { - biggestGroup = groups[key]; + if (forceChangeActiveNode && biggestGroup.length > 1 && activeNode === biggestGroup[0].node) { + // Use random node from which are synced + activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node; + } else { + // Use node with minimum ping among which are synced + activeNode = biggestGroup[0].node; } - }); + } - // All the nodes from the biggestGroup list are considered to be in sync, all the others are not - this.liveNodes.forEach((node) => { - node.outOfSync = !biggestGroup.includes(node); - }); + socket.reviseConnection(liveNodes); - outOfSyncCount = this.liveNodes.length - biggestGroup.length; + const unavailableCount = nodesList.length - liveNodes.length; + const supportedCount = liveNodes.length - outOfSyncCount; - biggestGroup.sort((a, b) => a.ping - b.ping); - this.liveNodes.sort((a, b) => a.ping - b.ping); + let nodesInfoString = ''; - if (forceChangeActiveNode && biggestGroup.length > 1 && this.activeNode === biggestGroup[0].node) { - // Use random node from which are synced - this.activeNode = biggestGroup[validator.getRandomIntInclusive(1, biggestGroup.length - 1)].node; - } else { - // Use node with minimum ping among which are synced - this.activeNode = biggestGroup[0].node; + if (unavailableCount) { + nodesInfoString += `, ${unavailableCount} nodes didn't respond`; } - } - socket.reviseConnection(this.liveNodes); - - const unavailableCount = this.nodesList.length - this.liveNodes.length; - const supportedCount = this.liveNodes.length - outOfSyncCount; - - let nodesInfoString = ''; + if (outOfSyncCount) { + nodesInfoString += `, ${outOfSyncCount} nodes are not synced`; + } - if (unavailableCount) { - nodesInfoString += `, ${unavailableCount} nodes didn't respond`; + logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${activeNode}.`); } + } catch (e) { + logger.warn('[ADAMANT js-api] Health check: Error in checkNodes(), ' + e); + } - if (outOfSyncCount) { - nodesInfoString += `, ${outOfSyncCount} nodes are not synced`; - } + isCheckingNodes = false; + } - logger.log(`[ADAMANT js-api] Health check: Found ${supportedCount} supported and synced nodes${nodesInfoString}. Active node is ${this.activeNode}.`); - } - } catch (e) { - logger.warn('[ADAMANT js-api] Health check: Error in checkNodes(), ' + e); + if (checkHealthAtStartup) { + changeNodes(true); + + setInterval( + () => changeNodes(true), + CHECK_NODES_INTERVAL, + ); } - this.isCheckingNodes = false; -} + return { + /** + * @return {string} Current active node, f. e. http://88.198.156.44:36666 + */ + node: () => activeNode, + changeNodes, + }; +}; async function getIP(url) { try { @@ -199,12 +200,8 @@ async function getIP(url) { */ function checkNode(url) { return axios.get(url) - .then(function(response) { - return {status: response.data}; - }) - .catch(function(error) { - return false; - }); + .then((response) => ({status: response.data})) + .catch((err) => false); } function unixTimestamp() { From b812fea1b27bd91a9ade0f8c3b501ca0cb166413 Mon Sep 17 00:00:00 2001 From: martiliones Date: Mon, 28 Mar 2022 00:05:59 +0600 Subject: [PATCH 19/31] test: add jest as dev dependency --- package.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 02501b2..9ce49f2 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "adamant-api", "version": "1.5.0", "description": "REST API for ADAMANT Blockchain", - "main": "src/index.js", + "main": "src/index.js", "scripts": { "test": "jest", - "lint": "eslint src", - "lint:fix": "eslint --fix src", + "lint": "eslint src", + "lint:fix": "eslint --fix src", "prepare": "husky install" }, "author": "ADAMANT Foundation (https://adamant.im)", @@ -68,6 +68,7 @@ "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-jest": "^26.1.0", - "husky": "^7.0.4" + "husky": "^7.0.4", + "jest": "^27.5.1" } } From f02b1d5aa45b73c7791d5b5f7b6d6a2f47572cb5 Mon Sep 17 00:00:00 2001 From: martiliones Date: Mon, 28 Mar 2022 01:49:26 +0600 Subject: [PATCH 20/31] test: write tests for the validator --- src/helpers/tests/validator.test.js | 321 ++++++++++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 src/helpers/tests/validator.test.js diff --git a/src/helpers/tests/validator.test.js b/src/helpers/tests/validator.test.js new file mode 100644 index 0000000..e9d211f --- /dev/null +++ b/src/helpers/tests/validator.test.js @@ -0,0 +1,321 @@ +const validator = require('../validator'); + +describe('isNumeric', () => { + test('Should return false for a number', () => { + expect(validator.isNumeric(3)).toBe(false); + }); + + test('Should return false for Infinity', () => { + expect(validator.isNumeric(Infinity)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.isNumeric({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.isNumeric(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.isNumeric(undefined)).toBe(false); + }); + + test('Should return false for `n3.14`', () => { + expect(validator.isNumeric('n3.14')).toBe(false); + }); + + test('Should return false for `3,14`', () => { + expect(validator.isNumeric('3,14')).toBe(true); + }); + + test('Should return true for `3.14`', () => { + expect(validator.isNumeric('3.14')).toBe(true); + }); + + test('Should return true for ` 3.14`', () => { + expect(validator.isNumeric(' 3.14')).toBe(true); + }); +}); + +describe('validatePassPhrase', () => { + test('Should return false for a number', () => { + expect(validator.validatePassPhrase(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validatePassPhrase({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validatePassPhrase(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validatePassPhrase(undefined)).toBe(false); + }); + + test('Should return false for a too short string', () => { + expect(validator.validatePassPhrase('short')).toBe(false); + }); + + test('Should return true for a long string', () => { + expect(validator.validatePassPhrase('word '.repeat(12))).toBe(true); + }); +}); + +describe('validateAdmAddress', () => { + test('Should return false for a number', () => { + expect(validator.validateAdmAddress(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validateAdmAddress({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validateAdmAddress(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validateAdmAddress(undefined)).toBe(false); + }); + + test('Should return false for U123', () => { + expect(validator.validateAdmAddress('U123')).toBe(false); + }); + + test('Should return false for ` U123456`', () => { + expect(validator.validateAdmAddress(' U123456')).toBe(false); + }); + + test('Should return false for `U123213N123`', () => { + expect(validator.validateAdmAddress('U123213N123')).toBe(false); + }); + + test('Should return true for U123456', () => { + expect(validator.validateAdmAddress('U1234506')).toBe(true); + }); + + test('Should return true for U01234561293812931283918239', () => { + expect(validator.validateAdmAddress('U01234561293812931283918239')).toBe(true); + }); +}); + +describe('validateAdmPublicKey', () => { + test('Should return false for a number', () => { + expect(validator.validateAdmPublicKey(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validateAdmPublicKey({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validateAdmPublicKey(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validateAdmPublicKey(undefined)).toBe(false); + }); + + test('Should return false for a short string', () => { + expect(validator.validateAdmPublicKey('0f')).toBe(false); + }); + + test('Should return false for a string that contains `L`', () => { + expect(validator.validateAdmPublicKey('Le003f782cd1c1c84a6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(false); + }); + + test('Should return true for a public key that starts with a number', () => { + expect(validator.validateAdmPublicKey('4e003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(true); + }); + + test('Should return true for a public key that starts with a letter', () => { + expect(validator.validateAdmPublicKey('e4003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(true); + }); +}); + +describe('validateAdmVoteForAddress', () => { + test('Should return false for a number', () => { + expect(validator.validateAdmVoteForAddress(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validateAdmVoteForAddress({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validateAdmVoteForAddress(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validateAdmVoteForAddress(undefined)).toBe(false); + }); + + test('Should return false for a short string', () => { + expect(validator.validateAdmVoteForAddress('0f')).toBe(false); + }); + + test('Should return false for a string that starts with `L`', () => { + expect(validator.validateAdmVoteForAddress('L01234561293812931283918239')).toBe(false); + }); + + test('Should return false for an address that starts with a number', () => { + expect(validator.validateAdmVoteForAddress('0U1234561293812931283918239')).toBe(false); + }); + + test('Should return false for an address that starts with a letter', () => { + expect(validator.validateAdmVoteForAddress('U01234561293812931283918239')).toBe(false); + }); + + test('Should return true for an address with a plus', () => { + expect(validator.validateAdmVoteForAddress('+U01234561293812931283918239')).toBe(true); + }); + + test('Should return true for an address with a minus', () => { + expect(validator.validateAdmVoteForAddress('+U01234561293812931283918239')).toBe(true); + }); +}); + +describe('validateAdmVoteForPublicKey', () => { + test('Should return false for a number', () => { + expect(validator.validateAdmVoteForPublicKey(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validateAdmVoteForPublicKey({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validateAdmVoteForPublicKey(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validateAdmVoteForPublicKey(undefined)).toBe(false); + }); + + test('Should return false for a short string', () => { + expect(validator.validateAdmVoteForPublicKey('0f')).toBe(false); + }); + + test('Should return false for a string that starts with `L`', () => { + expect(validator.validateAdmVoteForPublicKey('+L4e003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(false); + }); + + test('Should return false for a public key that starts with a number', () => { + expect(validator.validateAdmVoteForPublicKey('4e003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(false); + }); + + test('Should return false for a public key that starts with a letter', () => { + expect(validator.validateAdmVoteForPublicKey('e4003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(false); + }); + + test('Should return true for a public key with a plus', () => { + expect(validator.validateAdmVoteForPublicKey('+4e003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(true); + }); + + test('Should return true for a public key with a minus', () => { + expect(validator.validateAdmVoteForPublicKey('+4e003f782cd1c1c84A6767a871321af2ecdb3da8d8f6b8d1f13179835b6ec432')).toBe(true); + }); +}); + +describe('validateAdmVoteForDelegateName', () => { + test('Should return false for a number', () => { + expect(validator.validateAdmVoteForDelegateName(3)).toBe(false); + }); + + test('Should return false for an object', () => { + expect(validator.validateAdmVoteForDelegateName({})).toBe(false); + }); + + test('Should return false for undefined', () => { + expect(validator.validateAdmVoteForDelegateName(undefined)).toBe(false); + }); + + test('Should return false for NaN', () => { + expect(validator.validateAdmVoteForDelegateName(undefined)).toBe(false); + }); + + test('Should return false for a short string', () => { + expect(validator.validateAdmVoteForDelegateName('0f')).toBe(false); + }); + + test('Should return false for a vote without delegate name', () => { + expect(validator.validateAdmVoteForDelegateName('+')).toBe(false); + }); + + test('Should return false for a too long delegate name', () => { + expect(validator.validateAdmVoteForDelegateName('+e003f782cd1c1c84A6767a871321af2e')).toBe(false); + }); + + test('Should return false for a vote that starts with a number', () => { + expect(validator.validateAdmVoteForDelegateName('4darksinc')).toBe(false); + }); + + test('Should return false for a vote that starts with a letter', () => { + expect(validator.validateAdmVoteForDelegateName('darksinc')).toBe(false); + }); + + test('Should return true for a delegate name with a plus', () => { + expect(validator.validateAdmVoteForDelegateName('+darksinc')).toBe(true); + }); + + test('Should return true for a delegate name with a minus', () => { + expect(validator.validateAdmVoteForDelegateName('+darksinc')).toBe(true); + }); +}); + +describe('validateMessage', () => { + test('Result should be false for a number message', () => { + expect(validator.validateMessage(3).result).toBe(false); + }); + + test('Result should be false for an object message', () => { + expect(validator.validateMessage({}).result).toBe(false); + }); + + test('Result should be true for a string message', () => { + expect(validator.validateMessage('').result).toBe(true); + }); + + test('Result should be false for a string rich message', () => { + expect(validator.validateMessage('', 2).result).toBe(false); + }); + + test('Result should be false for a string signal message', () => { + expect(validator.validateMessage('', 3).result).toBe(false); + }); + + test('Result should be false for an empty json rich message', () => { + expect(validator.validateMessage('{}', 2).result).toBe(false); + }); + + test('Result should be false for an empty json signal message', () => { + expect(validator.validateMessage('{}', 3).result).toBe(false); + }); + + test('Result should be true for a json rich message with the given amount', () => { + expect(validator.validateMessage('{"amount": "0.13"}', 2).result).toBe(true); + }); + + test('Result should be true for a json signal message with the given amount', () => { + expect(validator.validateMessage('{"amount": "0.13"}', 3).result).toBe(true); + }); + + test('Result should be false for a json rich message with upercase coin name', () => { + expect(validator.validateMessage('{"amount": "0.13", "type": "ETH_transaction"}', 2).result).toBe(false); + }); + + test('Result should be false for a json signal message with upercase coin name', () => { + expect(validator.validateMessage('{"amount": "0.13", "type": "ETH_transaction"}', 3).result).toBe(false); + }); + + test('Result should be true for a json rich message with lowercase coin name', () => { + expect(validator.validateMessage('{"amount": "0.13", "type": "eth_transaction"}', 2).result).toBe(true); + }); + + test('Result should be true for a json signal message with lowercase coin name', () => { + expect(validator.validateMessage('{"amount": "0.13", "type": "eth_transaction"}', 3).result).toBe(true); + }); +}); From 2717799a9be1a5d99c923a7585e7bcc30885b899 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 01:17:31 +0600 Subject: [PATCH 21/31] refactor: time module --- src/helpers/time.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/helpers/time.js b/src/helpers/time.js index c73b1a5..4504cac 100644 --- a/src/helpers/time.js +++ b/src/helpers/time.js @@ -1,18 +1,15 @@ const constants = require('./constants.js'); module.exports = { + getEpochTime(time) { + const startTime = time ?? Date.now(); - getEpochTime: function(time) { - if (time === undefined) { - time = Date.now(); - } - const d = constants.epochTime; - const t = d.getTime(); - return Math.floor((time - t) / 1000); - }, + const {epochTime} = constants; + const epochTimeMs = epochTime.getTime(); - getTime: function(time) { + return Math.floor((startTime - epochTimeMs) / 1000); + }, + getTime(time) { return this.getEpochTime(time); }, - }; From 2b33cde63635f7f0704617821a052cac67e72fdb Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:21:07 +0600 Subject: [PATCH 22/31] fix: chatGetBytes and minor changes --- src/helpers/transactionFormer.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/helpers/transactionFormer.js b/src/helpers/transactionFormer.js index f8e9b91..e03cd91 100644 --- a/src/helpers/transactionFormer.js +++ b/src/helpers/transactionFormer.js @@ -67,7 +67,6 @@ module.exports = { const transaction = { ...this.createBasicTransaction(details), recipientId: null, - amount: 0, asset: { state: { key: details.key, @@ -125,7 +124,7 @@ module.exports = { return transaction; }, - createVoteTransaction: function(data) { + createVoteTransaction(data) { const details = { ...data, transactionType: constants.transactionTypes.VOTE, @@ -174,7 +173,7 @@ module.exports = { const getBytes = actions[type]; - if (getBytes) { + if (typeof getBytes === 'function') { const assetBytes = getBytes(transaction); return {assetBytes, assetSize: assetBytes.length}; @@ -306,10 +305,10 @@ module.exports = { return buf; }, - chatGetBytes: function(trs) { + chatGetBytes(trs) { let buf = Buffer.from([]); - const {message} = trs.asset.chat.message; + const {message} = trs.asset.chat; const messageBuf = Buffer.from(message, 'hex'); buf = Buffer.concat([buf, messageBuf]); From 58b20fde06cd0dbad3b3ba462cb21f1deb0de39b Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:22:15 +0600 Subject: [PATCH 23/31] style: remove the new lines and change logger property name --- src/helpers/logger.js | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/helpers/logger.js b/src/helpers/logger.js index a215f2a..4278e1e 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -1,41 +1,36 @@ const logger = { - errorLevel: 'log', - l: console, + logger: console, initLogger(errorLevel, log) { if (errorLevel) { this.errorLevel = errorLevel; } + if (log) { - this.l = log; + this.logger = log; } }, - error(str) { if (['error', 'warn', 'info', 'log'].includes(this.errorLevel)) { - this.l.error(str); + this.logger.error(str); } }, - warn(str) { if (['warn', 'info', 'log'].includes(this.errorLevel)) { - this.l.warn(str); + this.logger.warn(str); } }, - info(str) { if (['info', 'log'].includes(this.errorLevel)) { - this.l.info(str); + this.logger.info(str); } }, - log(str) { - if (['log'].includes(this.errorLevel)) { - this.l.log(str); + if (this.errorLevel === 'log') { + this.logger.log(str); } }, - }; module.exports = logger; From 96002b05e02ecc6c6fe4ef96892318179f9f0895 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:23:47 +0600 Subject: [PATCH 24/31] refactor(keys): rewrite the code to ES6+ and simplify code --- src/helpers/keys.js | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/helpers/keys.js b/src/helpers/keys.js index 3d41fbf..9df0573 100644 --- a/src/helpers/keys.js +++ b/src/helpers/keys.js @@ -1,39 +1,49 @@ const sodium = require('sodium-browserify-tweetnacl'); const crypto = require('crypto'); const Mnemonic = require('bitcore-mnemonic'); + const bignum = require('./bignumber.js'); module.exports = { - - createNewPassPhrase: function() { + createNewPassPhrase() { return new Mnemonic(Mnemonic.Words.ENGLISH).toString(); }, - - makeKeypairFromHash: function(hash) { + makeKeypairFromHash(hash) { const keypair = sodium.crypto_sign_seed_keypair(hash); + return { publicKey: keypair.publicKey, privateKey: keypair.secretKey, }; }, - - createHashFromPassPhrase: function(passPhrase) { + createHashFromPassPhrase(passPhrase) { const secretMnemonic = new Mnemonic(passPhrase, Mnemonic.Words.ENGLISH); - return crypto.createHash('sha256').update(secretMnemonic.toSeed().toString('hex'), 'hex').digest(); - }, - createKeypairFromPassPhrase: function(passPhrase) { + return crypto + .createHash('sha256') + .update( + secretMnemonic.toSeed().toString('hex'), + 'hex', + ) + .digest(); + }, + createKeypairFromPassPhrase(passPhrase) { const hash = this.createHashFromPassPhrase(passPhrase); + return this.makeKeypairFromHash(hash); }, + createAddressFromPublicKey(publicKey) { + const publicKeyHash = crypto + .createHash('sha256') + .update(publicKey, 'hex') + .digest(); - createAddressFromPublicKey: function(publicKey) { - const publicKeyHash = crypto.createHash('sha256').update(publicKey, 'hex').digest(); const temp = Buffer.alloc(8); + for (let i = 0; i < 8; i++) { temp[i] = publicKeyHash[7 - i]; } - return 'U' + bignum.fromBuffer(temp).toString(); - }, + return `U${bignum.fromBuffer(temp)}`; + }, }; From 0d7cca736e3c8bd898a628bc3e1e1c995970e79e Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:25:28 +0600 Subject: [PATCH 25/31] refactor(healthCheck): rewrite the code to ES6+ and move constant to local scope --- src/helpers/healthCheck.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/helpers/healthCheck.js b/src/helpers/healthCheck.js index fb97678..c471138 100644 --- a/src/helpers/healthCheck.js +++ b/src/helpers/healthCheck.js @@ -13,8 +13,9 @@ const HEIGHT_EPSILON = 5; // Used to group nodes by height and choose synced module.exports = (nodes, checkHealthAtStartup = true) => { const nodesList = nodes; let isCheckingNodes = false; - let liveNodes = []; - let activeNode = nodesList[0]; // Note: it may be not synced; and before first health check a node can reply with obsolete data + + // Note: it may be not synced; and before first health check a node can reply with obsolete data + let [activeNode] = nodesList; /** * Updates active nodes. If nodes are already updating, returns Promise of previous call @@ -39,7 +40,8 @@ module.exports = (nodes, checkHealthAtStartup = true) => { */ async function checkNodes(forceChangeActiveNode) { isCheckingNodes = true; - liveNodes = []; + + const liveNodes = []; try { for (const node of nodesList) { From 19fdba80b4e3c7ef43cd04bbdc54bf8e59089837 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:26:12 +0600 Subject: [PATCH 26/31] refactor(encryptor): use string instead of array --- src/helpers/encryptor.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/helpers/encryptor.js b/src/helpers/encryptor.js index ab983f0..265c64e 100644 --- a/src/helpers/encryptor.js +++ b/src/helpers/encryptor.js @@ -4,16 +4,14 @@ const ed2curve = require('ed2curve'); module.exports = { bytesToHex(bytes) { - const hex = []; + let hex = ''; - for (let i = 0; i < bytes.length; i++) { - hex.push( - (bytes[i] >>> 4).toString(16), - (bytes[i] & 0xF).toString(16), - ); + for (const byte of bytes) { + hex += (byte >>> 4).toString(16); + hex += (byte & 0xF).toString(16); } - return hex.join(''); + return hex; }, hexToBytes(hex) { const bytes = []; From 360d8ddc3270d63fd2b6a813033cfe2b6457651e Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:28:01 +0600 Subject: [PATCH 27/31] test: write tests for keys module --- src/helpers/tests/keys.test.js | 62 ++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/helpers/tests/keys.test.js diff --git a/src/helpers/tests/keys.test.js b/src/helpers/tests/keys.test.js new file mode 100644 index 0000000..eb4ee6d --- /dev/null +++ b/src/helpers/tests/keys.test.js @@ -0,0 +1,62 @@ +const keys = require('../keys'); +const {validateAdmAddress} = require('../validator'); + +describe('createNewPassPhrase', () => { + test('Should return string that contains more than 11 words', () => { + const passPhrase = keys.createNewPassPhrase(); + + expect(typeof passPhrase).toBe('string'); + expect( + passPhrase.split(' ').length, + ).toBeGreaterThanOrEqual(12); + }); +}); + +describe('makeKeypairFromHash', () => { + test('Should return object with buffers publicKey and privateKey', () => { + const passPhrase = keys.createNewPassPhrase(); + const hash = keys.createHashFromPassPhrase(passPhrase); + + const keypair = keys.makeKeypairFromHash(hash); + + expect(typeof keypair).toBe('object'); + + expect(Buffer.isBuffer(keypair.publicKey)).toBe(true); + expect(Buffer.isBuffer(keypair.privateKey)).toBe(true); + }); +}); + +describe('createHashFromPassPhrase', () => { + test('Should return different hashes for different passPhrases', () => { + const passPhrase = keys.createNewPassPhrase(); + const passPhrase2 = keys.createNewPassPhrase(); + + const hash = keys.createHashFromPassPhrase(passPhrase); + const hash2 = keys.createHashFromPassPhrase(passPhrase2); + + expect(hash.equals(hash2)).toBe(false); + }); +}); + +describe('createKeypairFromPassPhrase', () => { + test('Should return keypair with publicKey and privateKey', () => { + const passPhrase = keys.createNewPassPhrase(); + const keypair = keys.createKeypairFromPassPhrase(passPhrase); + + expect(typeof keypair).toBe('object'); + + expect(Buffer.isBuffer(keypair.publicKey)).toBe(true); + expect(Buffer.isBuffer(keypair.privateKey)).toBe(true); + }); +}); + +describe('createAddressFromPublicKey', () => { + test('Should return a string which matches the address pattern', () => { + const passPhrase = keys.createNewPassPhrase(); + const keypair = keys.createKeypairFromPassPhrase(passPhrase); + + const address = keys.createAddressFromPublicKey(keypair.publicKey); + + expect(validateAdmAddress(address)).toBe(true); + }); +}); From 4c04713fd5219b659ad31292693d536248eb8b31 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:30:32 +0600 Subject: [PATCH 28/31] test: write tests for logger module --- src/helpers/tests/logger.test.js | 203 +++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/helpers/tests/logger.test.js diff --git a/src/helpers/tests/logger.test.js b/src/helpers/tests/logger.test.js new file mode 100644 index 0000000..a463297 --- /dev/null +++ b/src/helpers/tests/logger.test.js @@ -0,0 +1,203 @@ +const logger = require('../logger'); + +describe('logger: log', () => { + const logLevel = 'log'; + + test('Should log log level', (done) => { + logger.initLogger(logLevel, { + log(str) { + expect(str).toBe('log'); + + done(); + }, + }); + + logger.log('log'); + }); + + test('Should log info level', (done) => { + logger.initLogger(logLevel, { + info(str) { + expect(str).toBe('info'); + + done(); + }, + }); + + logger.info('info'); + }); + + test('Should log warn level', (done) => { + logger.initLogger(logLevel, { + warn(str) { + expect(str).toBe('warn'); + + done(); + }, + }); + + logger.warn('warn'); + }); + + test('Should log error level', (done) => { + logger.initLogger(logLevel, { + error(str) { + expect(str).toBe('error'); + + done(); + }, + }); + + logger.error('error'); + }); +}); + +describe('logger: info', () => { + const logLevel = 'info'; + + test('Should not log log level', (done) => { + logger.initLogger(logLevel, { + log() { + done('Log level has been called'); + }, + }); + + logger.log('log'); + done(); + }); + + test('Should log info level', (done) => { + logger.initLogger(logLevel, { + info(str) { + expect(str).toBe('info'); + + done(); + }, + }); + + logger.info('info'); + }); + + test('Should log warn level', (done) => { + logger.initLogger(logLevel, { + warn(str) { + expect(str).toBe('warn'); + + done(); + }, + }); + + logger.warn('warn'); + }); + + test('Should log error level', (done) => { + logger.initLogger(logLevel, { + error(str) { + expect(str).toBe('error'); + + done(); + }, + }); + + logger.error('error'); + }); +}); + +describe('logger: warn', () => { + const logLevel = 'warn'; + + test('Should not log log level', (done) => { + logger.initLogger(logLevel, { + log() { + done('Log level has been called'); + }, + }); + + logger.log('log'); + done(); + }); + + test('Should not log info level', (done) => { + logger.initLogger(logLevel, { + info() { + done('Info level has been called'); + }, + }); + + logger.info('info'); + done(); + }); + + test('Should log warn level', (done) => { + logger.initLogger(logLevel, { + warn(str) { + expect(str).toBe('warn'); + + done(); + }, + }); + + logger.warn('warn'); + }); + + test('Should log error level', (done) => { + logger.initLogger(logLevel, { + error(str) { + expect(str).toBe('error'); + + done(); + }, + }); + + logger.error('error'); + }); +}); + +describe('logger: error', () => { + const logLevel = 'error'; + + test('Should not log log level', (done) => { + logger.initLogger(logLevel, { + log() { + done('Log level has been called'); + }, + }); + + logger.log('log'); + done(); + }); + + test('Should not log info level', (done) => { + logger.initLogger(logLevel, { + info() { + done('Info level has been called'); + }, + }); + + logger.info('info'); + done(); + }); + + test('Should not log warn level', (done) => { + logger.initLogger(logLevel, { + warn() { + done('Warn level has been called'); + }, + }); + + logger.warn('warn'); + done(); + }); + + test('Should log error level', (done) => { + logger.initLogger(logLevel, { + error(str) { + expect(str).toBe('error'); + + done(); + }, + }); + + logger.error('error'); + }); +}); From 6672ba420f0c5f55c9d0ebf4568e0210645b7e42 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:31:22 +0600 Subject: [PATCH 29/31] test: write unit tests for getting epoch time --- src/helpers/tests/time.test.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/helpers/tests/time.test.js diff --git a/src/helpers/tests/time.test.js b/src/helpers/tests/time.test.js new file mode 100644 index 0000000..48319a8 --- /dev/null +++ b/src/helpers/tests/time.test.js @@ -0,0 +1,10 @@ +const time = require('../time'); +const {epochTime} = require('../constants'); + +describe('getTime', () => { + test('Should return 0 for epoch time', () => { + expect( + time.getTime(epochTime.getTime()), + ).toBe(0); + }); +}); From 02487cbce6386b5e783a7c82cc8576800f269527 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:31:57 +0600 Subject: [PATCH 30/31] test: write unit tests for creating transactions --- src/helpers/tests/transactionFormer.test.js | 171 ++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/helpers/tests/transactionFormer.test.js diff --git a/src/helpers/tests/transactionFormer.test.js b/src/helpers/tests/transactionFormer.test.js new file mode 100644 index 0000000..2e4e12b --- /dev/null +++ b/src/helpers/tests/transactionFormer.test.js @@ -0,0 +1,171 @@ +const transactionFormer = require('../transactionFormer'); +const keys = require('../keys'); +const constants = require('../constants'); + +const passPhrase = keys.createNewPassPhrase(); +const keyPair = keys.createKeypairFromPassPhrase(passPhrase); + +describe('Create send transaction', () => { + const transactionType = constants.transactionTypes.SEND; + + test('Should create base transaction', () => { + const data = { + keyPair, + recipientId: 'U123456', + amount: 1, + }; + + const transaction = transactionFormer.createTransaction(transactionType, data); + + expect(transaction).toMatchObject({ + type: transactionType, + amount: 1, + recipientId: 'U123456', + }); + expect(transaction).toHaveProperty('timestamp'); + expect(transaction).toHaveProperty('senderPublicKey'); + expect(transaction).toHaveProperty('senderId'); + expect(transaction).toHaveProperty('asset'); + expect(transaction).toHaveProperty('signature'); + expect( + typeof transaction.signature, + ).toBe('string'); + }); +}); + +describe('Create vote transaction', () => { + const transactionType = constants.transactionTypes.VOTE; + + test('Should create base transaction', () => { + const data = { + keyPair, + votes: [], + }; + + const transaction = transactionFormer.createTransaction(transactionType, data); + + expect(transaction).toMatchObject({ + type: transactionType, + amount: 0, + }); + expect(transaction).toHaveProperty('timestamp'); + expect(transaction).toHaveProperty('recipientId'); + expect(transaction).toHaveProperty('senderPublicKey'); + expect(transaction).toHaveProperty('senderId'); + expect(transaction).toHaveProperty('asset'); + expect(transaction).toHaveProperty('signature'); + expect( + typeof transaction.signature, + ).toBe('string'); + }); +}); + +describe('Create delegate transaction', () => { + const transactionType = constants.transactionTypes.DELEGATE; + const username = 'admtest'; + + test('Should create base transaction', () => { + const data = { + keyPair, + username, + }; + + const transaction = transactionFormer.createTransaction(transactionType, data); + + expect(transaction).toMatchObject({ + type: transactionType, + amount: 0, + asset: { + delegate: { + username, + }, + }, + }); + expect(transaction).toHaveProperty('timestamp'); + expect(transaction).toHaveProperty('senderPublicKey'); + expect(transaction).toHaveProperty('senderId'); + expect(transaction).toHaveProperty('asset'); + expect(transaction).toHaveProperty('recipientId'); + expect(transaction).toHaveProperty('asset.delegate.publicKey'); + expect(transaction).toHaveProperty('signature'); + expect( + typeof transaction.signature, + ).toBe('string'); + }); +}); + +describe('Create chat transaction', () => { + const transactionType = constants.transactionTypes.CHAT_MESSAGE; + + test('Should create base transaction', () => { + const data = { + keyPair, + amount: 1, + message: 'Hello!', + own_message: null, + message_type: 0, + recipientId: 'U123456', + }; + + const transaction = transactionFormer.createTransaction(transactionType, data); + + expect(transaction).toMatchObject({ + type: transactionType, + recipientId: data.recipientId, + amount: 1, + asset: { + chat: { + message: data.message, + own_message: data.own_message, + type: data.message_type, + }, + }, + }); + expect(transaction).toHaveProperty('timestamp'); + expect(transaction).toHaveProperty('senderPublicKey'); + expect(transaction).toHaveProperty('senderId'); + expect(transaction).toHaveProperty('asset'); + expect(transaction).toHaveProperty('recipientId'); + expect(transaction).toHaveProperty('signature'); + expect( + typeof transaction.signature, + ).toBe('string'); + }); +}); + +describe('Create state transaction', () => { + const transactionType = constants.transactionTypes.STATE; + + test('Should create base transaction', () => { + const data = { + keyPair, + key: 'key', + value: 'value', + }; + + const transaction = transactionFormer.createTransaction(transactionType, data); + + expect(transaction).toMatchObject({ + type: transactionType, + recipientId: null, + amount: 0, + asset: { + state: { + key: data.key, + value: data.value, + type: 0, + }, + }, + }); + expect(transaction).toHaveProperty('timestamp'); + expect(transaction).toHaveProperty('senderPublicKey'); + expect(transaction).toHaveProperty('senderId'); + expect(transaction).toHaveProperty('asset'); + expect(transaction).toHaveProperty('recipientId'); + expect(transaction).toHaveProperty('signature'); + expect( + typeof transaction.signature, + ).toBe('string'); + }); +}); + From ecbe0b04a99cb562c6208d49270f027153e84da2 Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 31 Mar 2022 06:33:06 +0600 Subject: [PATCH 31/31] refactor(decodeMsg): rewrite the code to ES6+ --- src/groups/decodeMsg.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/groups/decodeMsg.js b/src/groups/decodeMsg.js index ddc6654..3b700c1 100644 --- a/src/groups/decodeMsg.js +++ b/src/groups/decodeMsg.js @@ -4,10 +4,13 @@ const keys = require('../helpers/keys'); module.exports = (msg, senderPublicKey, passPhrase, nonce) => { const keypair = keys.createKeypairFromPassPhrase(passPhrase); - let privateKey = keypair.privateKey; + + let {privateKey} = keypair; + if (typeof msg === 'string') { msg = hexToBytes(msg); } + if (typeof nonce === 'string') { nonce = hexToBytes(nonce); } @@ -19,9 +22,11 @@ module.exports = (msg, senderPublicKey, passPhrase, nonce) => { if (typeof privateKey === 'string') { privateKey = hexToBytes(privateKey); } + const DHPublicKey = ed2curve.convertPublicKey(senderPublicKey); const DHSecretKey = ed2curve.convertSecretKey(privateKey); const decrypted = nacl.box.open(msg, nonce, DHPublicKey, DHSecretKey); + return decrypted ? utf8ArrayToStr(decrypted) : ''; }; @@ -29,7 +34,7 @@ function hexToBytes(hexString = '') { const bytes = []; for (let c = 0; c < hexString.length; c += 2) { - bytes.push(parseInt(hexString.substr(c, 2), 16)); + bytes.push(parseInt(hexString.substring(c, c + 2), 16)); } return Uint8Array.from(bytes);