From 6d40ec0724f58b0dcb956572baf65abeb5fb46b3 Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 9 Feb 2022 21:53:11 +0600 Subject: [PATCH 01/12] Add delegateNew() --- groups/delegateNew.js | 70 +++++++++++++++++++++++++++++++++++++++++++ helpers/validator.js | 16 +++++++--- index.js | 4 ++- 3 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 groups/delegateNew.js diff --git a/groups/delegateNew.js b/groups/delegateNew.js new file mode 100644 index 0000000..c7448e4 --- /dev/null +++ b/groups/delegateNew.js @@ -0,0 +1,70 @@ +const axios = require('axios'); +const logger = require('../helpers/logger'); +const keys = require('../helpers/keys'); +const constants = require('../helpers/constants'); +const transactionFormer = require('../helpers/transactionFormer'); +const validator = require('../helpers/validator'); + +const DEFAULT_SEND_TOKENS_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries + +module.exports = (nodeManager) => { + /** + * 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. + * Address is preferred, as if we get public key, we should derive address from it. + * @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 + */ + return (passPhrase, username, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { + + let transaction; + + try { + if (!validator.validatePassPhrase(passPhrase)) + return validator.badParameter('passPhrase') + + const keyPair = keys.createKeypairFromPassPhrase(passPhrase); + + if (!validator.validateDelegateName(username)) + return validator.badParameter('username') + + const type = constants.transactionTypes.DELEGATE; + + const data = { + type, + keyPair, + username, + }; + + transaction = transactionFormer.createTransaction(type, data); + + } catch (e) { + + return validator.badParameter('#exception_catched#', e) + + } + + let url = nodeManager.node() + '/api/delegates'; + return axios.post(url, transaction) + .then(function (response) { + return validator.formatRequestResults(response, true) + }) + .catch(function (error) { + let logMessage = `[ADAMANT js-api] New 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(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/helpers/validator.js b/helpers/validator.js index 4483aaf..4997f3a 100644 --- a/helpers/validator.js +++ b/helpers/validator.js @@ -84,24 +84,24 @@ module.exports = { let json = this.tryParseJSON(message) - if (!json) + if (!json) return { result: false, 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) return { result: false, 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` - } + } } } @@ -110,6 +110,14 @@ module.exports = { } }, + validateDelegateName(name) { + if (typeof name !== 'string') { + return false; + } + + return /^[\w!@$&_]*$/.test(name); + }, + AdmToSats(amount) { return BigNumber(String(amount)).multipliedBy(constants.SAT).integerValue().toNumber() }, diff --git a/index.js b/index.js index 97bcc82..d14a253 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const constants = require('./helpers/constants.js'); const get = require('./groups/get'); const getPublicKey = require('./groups/getPublicKey'); const decodeMsg = require('./groups/decodeMsg'); +const delegateNew = require('./groups/delegateNew'); const sendTokens = require('./groups/sendTokens'); const sendMessage = require('./groups/sendMessage'); const healthCheck = require('./helpers/healthCheck'); @@ -19,12 +20,13 @@ module.exports = (params, log) => { log = log || console; logger.initLogger(params.logLevel, log); const nodeManager = healthCheck(params.node); - + return { get: get(nodeManager), getPublicKey: getPublicKey(nodeManager), sendTokens: sendTokens(nodeManager), sendMessage: sendMessage(nodeManager), + delegateNew: delegateNew(nodeManager), decodeMsg, eth, dash, From 4a8f4974ff87957b6d7e90d86a76f882e43a0f4a Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 9 Feb 2022 22:15:33 +0600 Subject: [PATCH 02/12] Add voteForDelegate() --- groups/voteForDelegate.js | 70 +++++++++++++++++++++++++++++++++++++++ helpers/constants.js | 3 +- helpers/validator.js | 11 ++++++ index.js | 2 ++ 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 groups/voteForDelegate.js diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js new file mode 100644 index 0000000..eb3301c --- /dev/null +++ b/groups/voteForDelegate.js @@ -0,0 +1,70 @@ +const axios = require('axios'); +const logger = require('../helpers/logger'); +const keys = require('../helpers/keys'); +const constants = require('../helpers/constants'); +const transactionFormer = require('../helpers/transactionFormer'); +const validator = require('../helpers/validator'); + +const DEFAULT_SEND_TOKENS_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries + +module.exports = (nodeManager) => { + /** + * 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. + * Address is preferred, as if we get public key, we should derive address from it. + * @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 + */ + return (passPhrase, votes, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { + + let transaction; + + try { + if (!validator.validatePassPhrase(passPhrase)) + return validator.badParameter('passPhrase') + + const keyPair = keys.createKeypairFromPassPhrase(passPhrase); + + if (!validator.validateAdmVotes(votes)) + return validator.badParameter('votes') + + const type = constants.transactionTypes.VOTE; + + const data = { + type, + keyPair, + votes, + }; + + transaction = transactionFormer.createTransaction(type, data); + + } catch (e) { + + return validator.badParameter('#exception_catched#', e) + + } + + let url = nodeManager.node() + '/api/accounts/delegates'; + return axios.post(url, transaction) + .then(function (response) { + return validator.formatRequestResults(response, true) + }) + .catch(function (error) { + let 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(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/helpers/constants.js b/helpers/constants.js index 55ed58e..561ac86 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -31,9 +31,10 @@ module.exports = { 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: /^(\+|-)[a-fA-F0-9]{64}$/, 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/helpers/validator.js b/helpers/validator.js index 4997f3a..e53cb3c 100644 --- a/helpers/validator.js +++ b/helpers/validator.js @@ -52,6 +52,17 @@ module.exports = { return true }, + validateAdmVotes(publicKeys) { + for (let i = publicKeys.length - 1; i >= 0; i--) { + const publicKey = publicKeys[i]; + + if (!publicKey || typeof(publicKey) !== 'string' || !constants.RE_ADM_VOTE.test(publicKey)) + return false + } + + return true + }, + validateIntegerAmount(amount) { if (!amount || typeof(amount) !== 'number' || isNaN(amount) || !Number.isSafeInteger(amount)) return false diff --git a/index.js b/index.js index d14a253..f680bda 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ const get = require('./groups/get'); const getPublicKey = require('./groups/getPublicKey'); const decodeMsg = require('./groups/decodeMsg'); const delegateNew = require('./groups/delegateNew'); +const voteForDelegate = require('./groups/voteForDelegate'); const sendTokens = require('./groups/sendTokens'); const sendMessage = require('./groups/sendMessage'); const healthCheck = require('./helpers/healthCheck'); @@ -27,6 +28,7 @@ module.exports = (params, log) => { sendTokens: sendTokens(nodeManager), sendMessage: sendMessage(nodeManager), delegateNew: delegateNew(nodeManager), + voteForDelegate: voteForDelegate(nodeManager), decodeMsg, eth, dash, From 6e0514435597076ddf0a1cacd253df6a40eae174 Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 9 Feb 2022 22:27:00 +0600 Subject: [PATCH 03/12] Refactoring --- groups/{delegateNew.js => newDelegate.js} | 10 ++++------ groups/voteForDelegate.js | 9 +++------ helpers/constants.js | 1 + helpers/validator.js | 2 +- index.js | 4 ++-- 5 files changed, 11 insertions(+), 15 deletions(-) rename groups/{delegateNew.js => newDelegate.js} (82%) diff --git a/groups/delegateNew.js b/groups/newDelegate.js similarity index 82% rename from groups/delegateNew.js rename to groups/newDelegate.js index c7448e4..6ca0626 100644 --- a/groups/delegateNew.js +++ b/groups/newDelegate.js @@ -9,13 +9,11 @@ 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 - * See https://github.com/Adamant-im/adamant/wiki/Transaction-Types#type-0-token-transfer-transaction + * Registers user account as delegate * @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 {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) + * @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 */ diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index eb3301c..189bfcb 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -9,13 +9,10 @@ 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 - * See https://github.com/Adamant-im/adamant/wiki/Transaction-Types#type-0-token-transfer-transaction + * 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} 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 {boolean} isAmountInADM If amount specified in ADM, or in sats (10^-8 ADM) + * @param {string[]} votes PublicKeys for upvote and downvote * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results */ diff --git a/helpers/constants.js b/helpers/constants.js index 561ac86..c8dbfa0 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -32,6 +32,7 @@ module.exports = { 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: /^(\+|-)[a-fA-F0-9]{64}$/, + RE_ADM_DELEGATE_NAME: /^[\w!@$&_]*$/, 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,})$/, diff --git a/helpers/validator.js b/helpers/validator.js index e53cb3c..1d9992a 100644 --- a/helpers/validator.js +++ b/helpers/validator.js @@ -126,7 +126,7 @@ module.exports = { return false; } - return /^[\w!@$&_]*$/.test(name); + return constants.RE_ADM_DELEGATE_NAME.test(name); }, AdmToSats(amount) { diff --git a/index.js b/index.js index f680bda..a016996 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ const constants = require('./helpers/constants.js'); const get = require('./groups/get'); const getPublicKey = require('./groups/getPublicKey'); const decodeMsg = require('./groups/decodeMsg'); -const delegateNew = require('./groups/delegateNew'); +const newDelegate = require('./groups/newDelegate'); const voteForDelegate = require('./groups/voteForDelegate'); const sendTokens = require('./groups/sendTokens'); const sendMessage = require('./groups/sendMessage'); @@ -27,7 +27,7 @@ module.exports = (params, log) => { getPublicKey: getPublicKey(nodeManager), sendTokens: sendTokens(nodeManager), sendMessage: sendMessage(nodeManager), - delegateNew: delegateNew(nodeManager), + newDelegate: newDelegate(nodeManager), voteForDelegate: voteForDelegate(nodeManager), decodeMsg, eth, From 0f7280635bed4200f251978922b7a30e558a3ff3 Mon Sep 17 00:00:00 2001 From: martiliones Date: Wed, 9 Feb 2022 22:28:22 +0600 Subject: [PATCH 04/12] Pump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 306cd89..49a38b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "adamant-api", - "version": "1.3.0", + "version": "1.4.0", "description": "REST API for ADAMANT Blockchain", "main": "index.js", "scripts": { From eb9f466bd4f9cb9ca55b5b338dc12628e66f723e Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 10 Feb 2022 14:52:45 +0600 Subject: [PATCH 05/12] Rename the constants --- groups/newDelegate.js | 4 ++-- groups/voteForDelegate.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/groups/newDelegate.js b/groups/newDelegate.js index 6ca0626..b9dc722 100644 --- a/groups/newDelegate.js +++ b/groups/newDelegate.js @@ -5,7 +5,7 @@ const constants = require('../helpers/constants'); const transactionFormer = require('../helpers/transactionFormer'); const validator = require('../helpers/validator'); -const DEFAULT_SEND_TOKENS_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries +const DEFAULT_NEW_DELEGATE_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries module.exports = (nodeManager) => { /** @@ -17,7 +17,7 @@ module.exports = (nodeManager) => { * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results */ - return (passPhrase, username, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { + return (passPhrase, username, maxRetries = DEFAULT_NEW_DELEGATE_RETRIES, retryNo = 0) => { let transaction; diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 189bfcb..62d0224 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -5,7 +5,7 @@ const constants = require('../helpers/constants'); const transactionFormer = require('../helpers/transactionFormer'); const validator = require('../helpers/validator'); -const DEFAULT_SEND_TOKENS_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries +const DEFAULT_VOTE_FOR_DELEGATE_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries module.exports = (nodeManager) => { /** @@ -16,7 +16,7 @@ module.exports = (nodeManager) => { * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results */ - return (passPhrase, votes, maxRetries = DEFAULT_SEND_TOKENS_RETRIES, retryNo = 0) => { + return (passPhrase, votes, maxRetries = DEFAULT_VOTE_FOR_DELEGATE_RETRIES, retryNo = 0) => { let transaction; From acadab3ff44edfb6b1dafbbfb3a6196c54529e8d Mon Sep 17 00:00:00 2001 From: martiliones Date: Thu, 10 Feb 2022 18:55:18 +0600 Subject: [PATCH 06/12] Test delegate names for length and lowercase --- helpers/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/constants.js b/helpers/constants.js index c8dbfa0..cea2cc7 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -32,7 +32,7 @@ module.exports = { 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: /^(\+|-)[a-fA-F0-9]{64}$/, - RE_ADM_DELEGATE_NAME: /^[\w!@$&_]*$/, + 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,})$/, From 6b7bf7d19d6087b55cac9e2720afe8ad391786ce Mon Sep 17 00:00:00 2001 From: martiliones Date: Fri, 11 Feb 2022 21:03:10 +0600 Subject: [PATCH 07/12] Allow votes[] to include publicKeys and ADM addresses and delegate names (mixed). Exclude duplicates. --- groups/voteForDelegate.js | 61 +++++++++++++++++++++++++++++++++++---- helpers/constants.js | 6 ++-- helpers/validator.js | 24 ++++++++++----- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 62d0224..7bb29e6 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -1,4 +1,5 @@ const axios = require('axios'); +const get = require('./get'); const logger = require('../helpers/logger'); const keys = require('../helpers/keys'); const constants = require('../helpers/constants'); @@ -7,34 +8,82 @@ const validator = require('../helpers/validator'); const DEFAULT_VOTE_FOR_DELEGATE_RETRIES = 4; // How much re-tries for send tokens requests by default. Total 4+1 tries +const publicKeysCache = { }; + module.exports = (nodeManager) => { /** * 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 for upvote and downvote + * @param {string[]} votes PublicKeys, ADM addresses and delegate names for upvote and downvote * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results */ - return (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); - if (!validator.validateAdmVotes(votes)) - return validator.badParameter('votes') + const uniqueVotes = []; + + for (let i = votes.length - 1; i >= 0; i--) { + const vote = votes[i]; + const voteName = vote.slice(1); + const voteDirection = vote.charAt(0); + + const cachedPublicKey = publicKeysCache[voteName]; + + if (cachedPublicKey) { + votes[i] = `${voteDirection}${cachedPublicKey}`; + continue; + } + + if (validator.validateAdmVoteForAddress(vote)) { + const res = await get(nodeManager)('/accounts', { address: voteName }); + + if (res.success) { + const publicKey = res.data.account.publicKey; + votes[i] = `${voteDirection}${publicKey}`; + console.log(publicKey); + publicKeysCache[voteName] = publicKey; + } else { + logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); + return validator.badParameter('votes') + } + } else if (validator.validateAdmVoteForDelegateName(vote)) { + const res = await get(nodeManager)('/delegates/get', { username: voteName }); + + if (res.success) { + const publicKey = res.data.delegate.publicKey; + votes[i] = `${voteDirection}${publicKey}`; + publicKeysCache[voteName] = publicKey; + } else { + logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); + return validator.badParameter('votes') + } + } else if (!validator.validateAdmVoteForPublicKey(vote)) { + return validator.badParameter('votes') + } + + // Exclude duplicates + const foundCopy = uniqueVotes.findIndex((v) => v.slice(1) === votes[i].slice(1)); + + if (foundCopy === -1) { + uniqueVotes.push(votes[i]); + } + } const type = constants.transactionTypes.VOTE; const data = { type, keyPair, - votes, + votes: uniqueVotes, }; transaction = transactionFormer.createTransaction(type, data); diff --git a/helpers/constants.js b/helpers/constants.js index cea2cc7..bee5c31 100644 --- a/helpers/constants.js +++ b/helpers/constants.js @@ -31,8 +31,10 @@ module.exports = { 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: /^(\+|-)[a-fA-F0-9]{64}$/, - RE_ADM_DELEGATE_NAME: /^[a-z0-9!@$&_]{1,20}*$/, + 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,})$/, diff --git a/helpers/validator.js b/helpers/validator.js index 1d9992a..f73aa5c 100644 --- a/helpers/validator.js +++ b/helpers/validator.js @@ -52,15 +52,25 @@ module.exports = { return true }, - validateAdmVotes(publicKeys) { - for (let i = publicKeys.length - 1; i >= 0; i--) { - const publicKey = publicKeys[i]; + validateAdmVoteForPublicKey(publicKey) { + if (!publicKey || typeof(publicKey) !== 'string' || !constants.RE_ADM_VOTE_FOR_PUBLIC_KEY.test(publicKey)) + return false + else + return true + }, - if (!publicKey || typeof(publicKey) !== 'string' || !constants.RE_ADM_VOTE.test(publicKey)) - return false - } + validateAdmVoteForAddress(address) { + if (!address || typeof(address) !== 'string' || !constants.RE_ADM_VOTE_FOR_ADDRESS.test(address)) + return false + else + return true + }, - return true + validateAdmVoteForDelegateName(delegateName) { + if (!delegateName || typeof(delegateName) !== 'string' || !constants.RE_ADM_VOTE_FOR_DELEGATE_NAME.test(delegateName)) + return false + else + return true }, validateIntegerAmount(amount) { From 13a20a218422a845af5a03b703af6f8502611a40 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 12 Feb 2022 08:40:58 +0600 Subject: [PATCH 08/12] Remove console.log() --- groups/voteForDelegate.js | 1 - 1 file changed, 1 deletion(-) diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 7bb29e6..0254b57 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -49,7 +49,6 @@ module.exports = (nodeManager) => { if (res.success) { const publicKey = res.data.account.publicKey; votes[i] = `${voteDirection}${publicKey}`; - console.log(publicKey); publicKeysCache[voteName] = publicKey; } else { logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); From 5bd56315f99fcfd80f8b0f8bc543f95bc6e06ace Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 12 Feb 2022 08:42:24 +0600 Subject: [PATCH 09/12] Update dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 49a38b9..04d8ec2 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,14 @@ "author": "RomanS, Aleksei Lebedev (https://adamant.im)", "license": "GPL-3.0", "dependencies": { - "axios": "^0.23.0", - "bignumber.js": "^9.0.1", + "axios": "^0.25.0", + "bignumber.js": "^9.0.2", "bitcoinjs-lib": "^5.2.0", - "bitcore-mnemonic": "^8.25.10", + "bitcore-mnemonic": "^8.25.25", "bytebuffer": "^5.0.1", "coininfo": "^5.1.0", "ed2curve": "^0.3.0", - "ethereumjs-util": "^7.1.3", + "ethereumjs-util": "^7.1.4", "hdkey": "^2.0.1", "socket.io-client": "^2.4.0", "sodium-browserify-tweetnacl": "^0.2.6" From 97d925d70a0e9c22d4a4340589f93d4223bd3208 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 12 Feb 2022 12:44:34 +0600 Subject: [PATCH 10/12] Update voteForDelegate() description --- groups/voteForDelegate.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 0254b57..3672c75 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -15,7 +15,8 @@ module.exports = (nodeManager) => { * 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 + * @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 */ From c6430deacfcc7ce99fce0a6846094427df9dc99a Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 12 Feb 2022 19:06:13 +0600 Subject: [PATCH 11/12] Refactoring --- groups/newDelegate.js | 52 ++++++++++++++++--------------- groups/voteForDelegate.js | 64 +++++++++++++++++++++------------------ helpers/validator.js | 15 ++------- 3 files changed, 66 insertions(+), 65 deletions(-) diff --git a/groups/newDelegate.js b/groups/newDelegate.js index b9dc722..1906f9a 100644 --- a/groups/newDelegate.js +++ b/groups/newDelegate.js @@ -17,18 +17,20 @@ module.exports = (nodeManager) => { * @param {number} maxRetries How much times to retry request * @returns {Promise} Request results */ - return (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') + if (!validator.validatePassPhrase(passPhrase)) { + return validator.badParameter('passPhrase'); + } const keyPair = keys.createKeypairFromPassPhrase(passPhrase); - if (!validator.validateDelegateName(username)) - return validator.badParameter('username') + if (!validator.validateDelegateName(username)) { + return validator.badParameter('username'); + } const type = constants.transactionTypes.DELEGATE; @@ -41,28 +43,30 @@ module.exports = (nodeManager) => { transaction = transactionFormer.createTransaction(type, data); } catch (e) { + return validator.badParameter('#exception_catched#', e); + } - return validator.badParameter('#exception_catched#', e) + const url = nodeManager.node() + '/api/delegates'; - } + try { + const response = await axios.post(url, transaction); + + return validator.formatRequestResults(response, true); + } catch (error) { + const logMessage = `[ADAMANT js-api] New 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}.`; - let url = nodeManager.node() + '/api/delegates'; - return axios.post(url, transaction) - .then(function (response) { - return validator.formatRequestResults(response, true) - }) - .catch(function (error) { - let logMessage = `[ADAMANT js-api] New 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(function () { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); - return validator.formatRequestResults(error, false) - }) + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .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/groups/voteForDelegate.js b/groups/voteForDelegate.js index 3672c75..901c22d 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -25,15 +25,15 @@ module.exports = (nodeManager) => { let transaction; try { - if (!validator.validatePassPhrase(passPhrase)) + if (!validator.validatePassPhrase(passPhrase)) { return validator.badParameter('passPhrase'); + } const keyPair = keys.createKeypairFromPassPhrase(passPhrase); const uniqueVotes = []; - for (let i = votes.length - 1; i >= 0; i--) { - const vote = votes[i]; + votes.forEach((vote, i) => { const voteName = vote.slice(1); const voteDirection = vote.charAt(0); @@ -41,6 +41,7 @@ module.exports = (nodeManager) => { if (cachedPublicKey) { votes[i] = `${voteDirection}${cachedPublicKey}`; + continue; } @@ -49,34 +50,38 @@ module.exports = (nodeManager) => { if (res.success) { const publicKey = res.data.account.publicKey; + votes[i] = `${voteDirection}${publicKey}`; publicKeysCache[voteName] = publicKey; } else { logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); - return validator.badParameter('votes') + + return validator.badParameter('votes'); } } else if (validator.validateAdmVoteForDelegateName(vote)) { const res = await get(nodeManager)('/delegates/get', { username: voteName }); if (res.success) { const publicKey = res.data.delegate.publicKey; + votes[i] = `${voteDirection}${publicKey}`; publicKeysCache[voteName] = publicKey; } else { logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); - return validator.badParameter('votes') + + return validator.badParameter('votes'); } } else if (!validator.validateAdmVoteForPublicKey(vote)) { - return validator.badParameter('votes') + return validator.badParameter('votes'); } // Exclude duplicates - const foundCopy = uniqueVotes.findIndex((v) => v.slice(1) === votes[i].slice(1)); + const foundCopy = uniqueVotes.find((v) => v.slice(1) === votes[i].slice(1)); - if (foundCopy === -1) { + if (!foundCopy) { uniqueVotes.push(votes[i]); } - } + }); const type = constants.transactionTypes.VOTE; @@ -87,30 +92,31 @@ module.exports = (nodeManager) => { }; transaction = transactionFormer.createTransaction(type, data); + } catch (error) { + return validator.badParameter('#exception_catched#', error) + } - } catch (e) { + const url = nodeManager.node() + '/api/accounts/delegates'; - return validator.badParameter('#exception_catched#', e) + try { + const response = await axios.post(url, transaction); - } + return validator.formatRequestResults(response, true); + } 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}.`; - let url = nodeManager.node() + '/api/accounts/delegates'; - return axios.post(url, transaction) - .then(function (response) { - return validator.formatRequestResults(response, true) - }) - .catch(function (error) { - let 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(function () { - return module.exports(nodeManager)(passPhrase, addressOrPublicKey, amount, isAmountInADM, maxRetries, ++retryNo) - }) - } - logger.warn(`${logMessage} No more attempts, returning error.`); - return validator.formatRequestResults(error, false) - }) + if (retryNo < maxRetries) { + logger.log(`${logMessage} Retrying…`); + return nodeManager.changeNodes() + .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/helpers/validator.js b/helpers/validator.js index f73aa5c..d1df0a8 100644 --- a/helpers/validator.js +++ b/helpers/validator.js @@ -53,24 +53,15 @@ module.exports = { }, validateAdmVoteForPublicKey(publicKey) { - if (!publicKey || typeof(publicKey) !== 'string' || !constants.RE_ADM_VOTE_FOR_PUBLIC_KEY.test(publicKey)) - return false - else - return true + return (publicKey && typeof(publicKey) === 'string' && constants.RE_ADM_VOTE_FOR_PUBLIC_KEY.test(publicKey)); }, validateAdmVoteForAddress(address) { - if (!address || typeof(address) !== 'string' || !constants.RE_ADM_VOTE_FOR_ADDRESS.test(address)) - return false - else - return true + return (address && typeof(address) === 'string' && constants.RE_ADM_VOTE_FOR_ADDRESS.test(address)); }, validateAdmVoteForDelegateName(delegateName) { - if (!delegateName || typeof(delegateName) !== 'string' || !constants.RE_ADM_VOTE_FOR_DELEGATE_NAME.test(delegateName)) - return false - else - return true + return (delegateName && typeof(delegateName) === 'string' && constants.RE_ADM_VOTE_FOR_DELEGATE_NAME.test(delegateName)); }, validateIntegerAmount(amount) { From 26f48c8ab0c671994e8b75b2bdaec649aefcc206 Mon Sep 17 00:00:00 2001 From: martiliones Date: Sat, 12 Feb 2022 20:37:50 +0600 Subject: [PATCH 12/12] Fix bug: Cached addresses were not checked for duplicates --- groups/voteForDelegate.js | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/groups/voteForDelegate.js b/groups/voteForDelegate.js index 901c22d..1d37d67 100644 --- a/groups/voteForDelegate.js +++ b/groups/voteForDelegate.js @@ -33,7 +33,8 @@ module.exports = (nodeManager) => { const uniqueVotes = []; - votes.forEach((vote, i) => { + for (let i = votes.length - 1; i >= 0; i--) { + const vote = votes[i]; const voteName = vote.slice(1); const voteDirection = vote.charAt(0); @@ -41,38 +42,36 @@ module.exports = (nodeManager) => { if (cachedPublicKey) { votes[i] = `${voteDirection}${cachedPublicKey}`; - - continue; - } - - if (validator.validateAdmVoteForAddress(vote)) { - const res = await get(nodeManager)('/accounts', { address: voteName }); - - if (res.success) { - const publicKey = res.data.account.publicKey; - - votes[i] = `${voteDirection}${publicKey}`; - publicKeysCache[voteName] = publicKey; - } else { - logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); - + } else { + if (validator.validateAdmVoteForAddress(vote)) { + const res = await get(nodeManager)('/accounts', { address: voteName }); + + if (res.success) { + const publicKey = res.data.account.publicKey; + + votes[i] = `${voteDirection}${publicKey}`; + publicKeysCache[voteName] = publicKey; + } else { + logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); + + return validator.badParameter('votes'); + } + } else if (validator.validateAdmVoteForDelegateName(vote)) { + const res = await get(nodeManager)('/delegates/get', { username: voteName }); + + if (res.success) { + const publicKey = res.data.delegate.publicKey; + + votes[i] = `${voteDirection}${publicKey}`; + publicKeysCache[voteName] = publicKey; + } else { + logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); + + return validator.badParameter('votes'); + } + } else if (!validator.validateAdmVoteForPublicKey(vote)) { return validator.badParameter('votes'); } - } else if (validator.validateAdmVoteForDelegateName(vote)) { - const res = await get(nodeManager)('/delegates/get', { username: voteName }); - - if (res.success) { - const publicKey = res.data.delegate.publicKey; - - votes[i] = `${voteDirection}${publicKey}`; - publicKeysCache[voteName] = publicKey; - } else { - logger.warn(`[ADAMANT js-api] Failed to get public key for ${vote}. ${res.errorMessage}.`); - - return validator.badParameter('votes'); - } - } else if (!validator.validateAdmVoteForPublicKey(vote)) { - return validator.badParameter('votes'); } // Exclude duplicates @@ -81,7 +80,7 @@ module.exports = (nodeManager) => { if (!foundCopy) { uniqueVotes.push(votes[i]); } - }); + } const type = constants.transactionTypes.VOTE;