From 4207f957902d4277b5b165c061dfde6c41d63a45 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 22 Mar 2026 17:23:38 +0100 Subject: [PATCH] Harden MM pool flow and frontend guards --- config_examples/peya.json | 4 +-- lib/apiInterfaces.js | 17 +++++----- lib/charts.js | 4 ++- lib/pool.js | 63 ++++++++++++++++++++++++++++---------- website_example/index.html | 1 + 5 files changed, 62 insertions(+), 27 deletions(-) diff --git a/config_examples/peya.json b/config_examples/peya.json index 7f81dd6..00f3fdd 100644 --- a/config_examples/peya.json +++ b/config_examples/peya.json @@ -3,8 +3,8 @@ "coin": "Peya", "symbol": "PEY", - "coinUnits": 1000000000000, - "coinDecimalPlaces": 12, + "coinUnits": 100000000, + "coinDecimalPlaces": 8, "coinDifficultyTarget": 120, "daemonType": "default", diff --git a/lib/apiInterfaces.js b/lib/apiInterfaces.js index cc4bcca..68ffe8e 100644 --- a/lib/apiInterfaces.js +++ b/lib/apiInterfaces.js @@ -9,19 +9,20 @@ var http = require('http'); var https = require('https'); -function jsonHttpRequest (host, port, data, callback, path) { +function jsonHttpRequest (host, port, data, callback, path, extraHeaders) { path = path || '/json_rpc'; callback = callback || function () {}; + extraHeaders = extraHeaders || {}; var options = { hostname: host, port: port, path: path, method: data ? 'POST' : 'GET', - headers: { + headers: Object.assign({ 'Content-Length': data.length, 'Content-Type': 'application/json', 'Accept': 'application/json' - } + }, extraHeaders) }; var req = (port === 443 ? https : http) .request(options, function (res) { @@ -51,7 +52,7 @@ function jsonHttpRequest (host, port, data, callback, path) { /** * Send RPC request **/ -function rpc (host, port, method, params, callback) { +function rpc (host, port, method, params, callback, extraHeaders) { var data = JSON.stringify({ id: "0", jsonrpc: "2.0", @@ -64,7 +65,7 @@ function rpc (host, port, method, params, callback) { return; } callback(replyJson.error, replyJson.result); - }); + }, '/json_rpc', extraHeaders); } /** @@ -99,11 +100,11 @@ module.exports = function (daemonConfig, walletConfig, poolApiConfig) { batchRpcDaemon: function (batchArray, callback) { batchRpc(daemonConfig.host, daemonConfig.port, batchArray, callback); }, - rpcDaemon: function (method, params, callback, serverConfig) { + rpcDaemon: function (method, params, callback, serverConfig, extraHeaders) { if (serverConfig) { - rpc(serverConfig.host, serverConfig.port, method, params, callback); + rpc(serverConfig.host, serverConfig.port, method, params, callback, extraHeaders); } else { - rpc(daemonConfig.host, daemonConfig.port, method, params, callback); + rpc(daemonConfig.host, daemonConfig.port, method, params, callback, extraHeaders); } }, rpcWallet: function (method, params, callback) { diff --git a/lib/charts.js b/lib/charts.js index 51f76c3..60f57ed 100644 --- a/lib/charts.js +++ b/lib/charts.js @@ -366,6 +366,7 @@ function getPoolChartsData (callback) { **/ function getUserChartsData (address, paymentsData, callback) { let stats = {}; + let userChartsConfig = config.charts && config.charts.user ? config.charts.user : {}; let chartsFuncs = { hashrate: function (callback) { getUserHashrateChartData(address, function (data) { @@ -378,7 +379,8 @@ function getUserChartsData (address, paymentsData, callback) { } }; for (let chartName in chartsFuncs) { - if (!config.charts.user[chartName].enabled) { + let chartConfig = userChartsConfig[chartName]; + if (chartConfig && chartConfig.enabled === false) { delete chartsFuncs[chartName]; } } diff --git a/lib/pool.js b/lib/pool.js index 5f55a64..9fc8d25 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -864,12 +864,13 @@ function IsBannedIp (ip) { } } -function recordShareData (miner, job, shareDiff, blockCandidate, hashHex, shareType, blockTemplate) { +function recordShareData (miner, job, shareDiff, blockCandidate, hashHex, shareType, blockTemplate, blockMeta) { let dateNow = Date.now(); let dateNowSeconds = dateNow / 1000 | 0; let coin = config.coin; let login = miner.login; - let job_height = job.height; + let job_height = blockMeta && blockMeta.height ? blockMeta.height : job.height; + let candidateHash = blockMeta && blockMeta.hash ? blockMeta.hash : hashHex; let workerName = miner.workerName; let rewardType = miner.rewardType; let updateScore; @@ -960,23 +961,39 @@ function recordShareData (miner, job, shareDiff, blockCandidate, hashHex, shareT return p + parseInt(workerShares[c]) }, 0); } - redisClient.zadd(coin + ':blocks:candidates', job_height, [ - rewardType, - login, - hashHex, - Date.now() / 1000 | 0, - blockTemplate.difficulty, - totalShares, - totalScore - ].join(':'), function (err, result) { + redisClient.zrangebyscore(coin + ':blocks:candidates', job_height, job_height, function (err, existingCandidates) { if (err) { - log('error', logSystem, 'Failed inserting block candidate %s \n %j', [hashHex, err]); + log('error', logSystem, 'Failed loading existing block candidates for height %d before inserting %s \n %j', [job_height, candidateHash, err]); + return; } + + let duplicateCandidate = existingCandidates.some(function (candidate) { + let parts = candidate.split(':'); + return parts.length >= 3 && parts[2] === candidateHash; + }); + + if (duplicateCandidate) { + return; + } + + redisClient.zadd(coin + ':blocks:candidates', job_height, [ + rewardType, + login, + candidateHash, + Date.now() / 1000 | 0, + blockTemplate.difficulty, + totalShares, + totalScore + ].join(':'), function (err, result) { + if (err) { + log('error', logSystem, 'Failed inserting block candidate %s \n %j', [candidateHash, err]); + } + }); }); notifications.sendToAll('blockFound', { 'HEIGHT': job_height, - 'HASH': hashHex, + 'HASH': candidateHash, 'DIFFICULTY': blockTemplate.difficulty, 'SHARES': totalShares, 'MINER': login.substring(0, 7) + '...' + login.substring(login.length - 7) @@ -1054,19 +1071,33 @@ function processShare (miner, job, blockTemplate, params) { let jobDifficulty = BigInt(job.difficulty); if (hashDiff >= blockDifficulty) { - + let submitHeaders = { + 'X-Hash-Difficulty': hashDiff.toString() + }; apiInterfaces.rpcDaemon('submitblock', [shareBuffer.toString('hex')], function (error, result) { if (error) { log('error', logSystem, 'Error submitting block at height %d from %s@%s, share type: "%s" - %j', [job.height, miner.login, miner.ip, shareType, error]); } else { let blockFastHash = utils.cnUtil.get_block_id(shareBuffer, getMiningParams().cnBlobType).toString('hex'); + let acceptedBlockMeta = null; + if (result && result.aux_accepted && result.aux_height && result.aux_block_id) { + acceptedBlockMeta = { + height: result.aux_height, + hash: result.aux_block_id + }; + } else if (result && result.block_id) { + acceptedBlockMeta = { + height: job.height, + hash: result.block_id + }; + } log('info', logSystem, 'Block %s found at height %d by miner %s@%s - submit result: %j', [blockFastHash.substr(0, 6), job.height, miner.login, miner.ip, result] ); - recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType, blockTemplate); + recordShareData(miner, job, hashDiff.toString(), true, blockFastHash, shareType, blockTemplate, acceptedBlockMeta); } - }, miningBackends.getMiningRpcConfig()); + }, miningBackends.getMiningRpcConfig(), submitHeaders); } else if (hashDiff < jobDifficulty) { log('warn', logSystem, 'Rejected low difficulty share of %s from %s@%s', [hashDiff.toString(), miner.login, miner.ip]); return false; diff --git a/website_example/index.html b/website_example/index.html index 5eed870..99505ba 100644 --- a/website_example/index.html +++ b/website_example/index.html @@ -219,6 +219,7 @@ function fetchLiveStats(endPoint, key) { // Fetch Block and Transaction Explorer Urls let xhrBlockExplorers; +let xhrMergedApis; function fetchBlockExplorers() { let apiURL = api + '/block_explorers';