diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 000000000..fe461b424 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,20 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v3 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v2 diff --git a/config_example.json b/config_example.json index 52d28f631..d25bd57e0 100644 --- a/config_example.json +++ b/config_example.json @@ -24,6 +24,15 @@ "checkThreshold": 500, "purgeInterval": 300 }, + "bannedAddresses": { + "enabled": false, + "banned": [ + "banned address 1", + "banned address 2", + "etcetera" + ] + }, + "redis": { "_disabled_socket": "/var/run/redis/redis.sock", "_socket": "Set socket to enable UNIX domain sockets, otherwise leave unset and set host and port.", diff --git a/init.js b/init.js index 07dfabbd6..a034d3ae7 100644 --- a/init.js +++ b/init.js @@ -290,8 +290,9 @@ var spawnPoolWorkers = function(){ var redisCommands = []; - // if its been less than 15 minutes since last share was submitted - var timeChangeSec = roundTo(Math.max(now - lastShareTime, 0) / 1000, 4); + // if its been less than 15 minutes since last share was submitted by any stratum + var lastShareTimeUnified = Math.max(redisCommands.push(['hget', msg.coin + ':lastSeen', workerAddress]), lastShareTime); + var timeChangeSec = roundTo(Math.max(now - lastShareTimeUnified, 0) / 1000, 4); //var timeChangeTotal = roundTo(Math.max(now - lastStartTime, 0) / 1000, 4); if (timeChangeSec < 900) { // loyal miner keeps mining :) diff --git a/libs/api.js b/libs/api.js index 1b4161174..6487ad817 100644 --- a/libs/api.js +++ b/libs/api.js @@ -29,6 +29,59 @@ module.exports = function(logger, portalConfig, poolConfigs){ res.end(JSON.stringify(data)); }); break; + case 'worker_balances': + res.header('Content-Type', 'application/json'); + if (req.url.indexOf("?") > 0) { + var url_parms = req.url.split("?"); + if (url_parms.length > 0) { + var address = url_parms[1] || null; + if (address != null && address.length > 0) { + address = address.split(".")[0]; + //portalStats.getPoolBalancesByAddress(address, function(balances) { + // res.end(JSON.stringify(balances)); + //}); + portalStats.getPoolBalancesByAddress(address, function(balances) { + var formattedBalances = {}; + + balances.forEach(function (balance) { + if (!formattedBalances[balance.pool]) { + formattedBalances[balance.pool] = { + name: balance.pool, + totalPaid: 0, + totalBalance: 0, + totalImmature: 0, + workers: [] + }; + } + + formattedBalances[balance.pool].totalPaid += balance.paid; + formattedBalances[balance.pool].totalBalance += balance.balance; + formattedBalances[balance.pool].totalImmature += balance.immature; + + formattedBalances[balance.pool].workers.push({ + name: balance.worker, + balance: balance.balance, + paid: balance.paid, + immature: balance.immature + }); + formattedBalances[balance.pool].totalPaid = (Math.round(formattedBalances[balance.pool].totalPaid * 100000000) / 100000000); + formattedBalances[balance.pool].totalBalance = (Math.round(formattedBalances[balance.pool].totalBalance * 100000000) / 100000000); + formattedBalances[balance.pool].totalImmature = (Math.round(formattedBalances[balance.pool].totalImmature * 100000000) / 100000000); + }); + + var finalBalances = Object.values(formattedBalances); + res.end(JSON.stringify(finalBalances)); + }); + } else { + res.end(JSON.stringify({ result: "error", message: "Invalid wallet address" })); + } + } else { + res.end(JSON.stringify({ result: "error", message: "Invalid URL parameters" })); + } + } else { + res.end(JSON.stringify({ result: "error", message: "URL parameters not found" })); + } + return; case 'payments': var poolBlocks = []; for(var pool in portalStats.stats.pools) { diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index b804411b1..cd2e9003b 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -7,6 +7,9 @@ var async = require('async'); var Stratum = require('stratum-pool'); var util = require('stratum-pool/lib/util.js'); var CreateRedisClient = require('./createRedisClient.js'); +var WAValidator = require('wallet-address-validator'); + +let badBlocks = {} module.exports = function(logger){ @@ -742,39 +745,59 @@ function SetupForPool(logger, poolOptions, setupFinished){ // update confirmations for round if (tx && tx.result) round.confirmations = parseInt((tx.result.confirmations || 0)); - + // look for transaction errors + // NOTE: We should combine these two if blocks into one since the only difference is in the logged message. if (tx.error && tx.error.code === -5){ - logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash); - round.category = 'kicked'; + if (undefined == badBlocks[round.txHash]) { + badBlocks[round.txHash] = 0 + } + + if (badBlocks[round.txHash] >= 15) { + logger.warning(logSystem, logComponent, 'ERROR: Daemon reports invalid transaction: ' + round.txHash) + delete badBlocks[round.txHash] + round.category = 'kicked' + } else { + badBlocks[round.txHash]++ + logger.warning(logSystem, logComponent, `Abandoned block ${round.txHash} check ${badBlocks[round.txHash]}/15`) + } return; } else if (!tx.result.details || (tx.result.details && tx.result.details.length === 0)){ - logger.warning(logSystem, logComponent, 'Daemon reports no details for transaction: ' + round.txHash); - round.category = 'kicked'; - return; - } - else if (tx.error || !tx.result){ - logger.error(logSystem, logComponent, 'Odd error with gettransaction ' + round.txHash + ' ' + JSON.stringify(tx)); + if (undefined == badBlocks[round.txHash]) { + badBlocks[round.txHash] = 0 + } + if (badBlocks[round.txHash] >= 15) { + logger.warning(logSystem, logComponent, 'ERROR: Daemon reports no details for transaction: ' + round.txHash) + delete badBlocks[round.txHash] + round.category = 'kicked' + } else { + badBlocks[round.txHash]++ + logger.warning(logSystem, logComponent, `Abandoned block ${round.txHash} check ${badBlocks[round.txHash]}/15`) + } return; } + // get the coin base generation tx - var generationTx = tx.result.details.filter(function(tx){ - return tx.address === poolOptions.address; - })[0]; - if (!generationTx && tx.result.details.length === 1){ - generationTx = tx.result.details[0]; + var generationTx = tx.result.details.filter(tx => tx.address === poolOptions.address)[0] + if (!generationTx && tx.result.details.length === 1) { + generationTx = tx.result.details[0] } if (!generationTx){ - logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + round.txHash); - return; + return logger.error(logSystem, logComponent, `ERROR: Missing output details to pool address for transaction ${round.txHash}`) } // get transaction category for round - round.category = generationTx.category; + round.category = generationTx.category // get reward for newly generated blocks if (round.category === 'generate' || round.category === 'immature') { round.reward = coinsRound(parseFloat(generationTx.amount || generationTx.value)); } + + // Clear blocks that previously triggered an attempted kick. + if (!round.txHash in badBlocks) { + logger.error(logSystem, logComponent, `${round.txHash} is no longer bad!`) + delete badBlocks[round.txHash] + } }); var canDeleteShares = function(r){ @@ -1417,11 +1440,15 @@ function SetupForPool(logger, poolOptions, setupFinished){ var getProperAddress = function(address){ - if (address.length >= 40){ - logger.warning(logSystem, logComponent, 'Invalid address '+address+', convert to address '+(poolOptions.invalidAddress || poolOptions.address)); - return (poolOptions.invalidAddress || poolOptions.address); + // Validation of Public and Identity addresses + var isvalid = WAValidator.validate(String(address).split(".")[0], 'VRSC'); + if(isvalid !== true){ +/* + // Validation of sapling addreses (disabled until paymentProcessor.js can handle sapling payments) + var isvalid = WAValidator.validate(String(address).split(".")[0], 'VRSC', 'sapling'); } - if (address.length <= 30) { + if (isvalid !== true){ +*/ logger.warning(logSystem, logComponent, 'Invalid address '+address+', convert to address '+(poolOptions.invalidAddress || poolOptions.address)); return (poolOptions.invalidAddress || poolOptions.address); } diff --git a/libs/poolWorker.js b/libs/poolWorker.js index f6f19eca9..1bc0a22e9 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -5,6 +5,7 @@ var net = require('net'); var MposCompatibility = require('./mposCompatibility.js'); var ShareProcessor = require('./shareProcessor.js'); var CreateRedisClient = require('./createRedisClient.js'); +var WAValidator = require('wallet-address-validator'); module.exports = function(logger){ @@ -134,23 +135,23 @@ module.exports = function(logger){ var shareProcessor = new ShareProcessor(logger, poolOptions); handlers.auth = function(port, workerName, password, authCallback){ - if (poolOptions.validateWorkerUsername !== true) - authCallback(true); - else { - pool.daemon.cmd('validateaddress', [String(workerName).split(".")[0]], function (results) { - var isValid = results.filter(function (r) { - if (r.response) - { - return r.response.isvalid; - } - else - { - return false; - } - }).length > 0; - authCallback(isValid); - }); + if (poolOptions.bannedAddresses.banned.indexOf(workerName) !== -1 && poolOptions.bannedAddresses.enabled == true) { + //Banned addresses return false if that option is enabled + isvalid = false; + } else if (poolOptions.validateWorkerUsername !== true) { + //Addresses are not checked for validity + isvalid = true; + } else { + //Validation of Public and Identity addresses + var isvalid = WAValidator.validate(String(workerName).split(".")[0], 'VRSC'); +/* + //Validation of sapling addreses (disabled until paymentProcessor.js can handle sapling payments) + if(isvalid !== true){ + var isvalid = WAValidator.validate(String(address).split(".")[0], 'VRSC', 'sapling'); } +*/ + } + authCallback(isvalid); }; handlers.share = function(isValidShare, isValidBlock, data){ diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index c8727a2e9..7b54b2c9a 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -26,7 +26,7 @@ module.exports = function(logger, poolConfig){ var logSystem = 'Pool'; var logComponent = coin; var logSubCat = 'Thread ' + (parseInt(forkId) + 1); - + var connection = CreateRedisClient(redisConfig); if (redisConfig.password) { connection.auth(redisConfig.password); @@ -69,11 +69,13 @@ module.exports = function(logger, poolConfig){ this.handleShare = function(isValidShare, isValidBlock, shareData) { var redisCommands = []; + var dateNow = Date.now(); if (isValidShare) { redisCommands.push(['hincrbyfloat', coin + ':shares:pbaasCurrent', shareData.worker, shareData.difficulty]); redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); + redisCommands.push(['hset', coin + ':lastSeen', shareData.worker, dateNow]); } else { redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); } @@ -81,7 +83,6 @@ module.exports = function(logger, poolConfig){ /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to generate hashrate for each worker and pool. */ - var dateNow = Date.now(); var hashrateData = [ isValidShare ? shareData.difficulty : -shareData.difficulty, shareData.worker, dateNow]; redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); diff --git a/libs/stats.js b/libs/stats.js index 61040376f..5879bef99 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -244,7 +244,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ async.each(_this.stats.pools, function(pool, pcb) { pindex++; var coin = String(_this.stats.pools[pool.name].name); - client.hscan(coin + ':shares:roundCurrent', 0, "match", a+"*", "count", 1000, function(error, result) { + client.hscan(coin + ':shares:roundCurrent', 0, "match", a+"*", "count", 50000, function(error, result) { if (error) { pcb(error); return; @@ -277,32 +277,32 @@ module.exports = function(logger, portalConfig, poolConfigs){ this.getBalanceByAddress = function(address, cback){ - var a = address.split(".")[0]; - + var a = address.split(".")[0]; + var client = redisClients[0].client, coins = redisClients[0].coins, balances = []; - - var totalHeld = parseFloat(0); - var totalPaid = parseFloat(0); + + var totalHeld = parseFloat(0); + var totalPaid = parseFloat(0); var totalImmature = parseFloat(0); - - async.each(_this.stats.pools, function(pool, pcb) { - var coin = String(_this.stats.pools[pool.name].name); - // get all immature balances from address - client.hscan(coin + ':immature', 0, "match", a+"*", "count", 10000, function(error, pends) { + + async.each(_this.stats.pools, function(pool, pcb) { + var coin = String(_this.stats.pools[pool.name].name); + // get all immature balances from address + client.hscan(coin + ':immature', 0, "match", a+"*", "count", 50000, function(error, pends) { // get all balances from address - client.hscan(coin + ':balances', 0, "match", a+"*", "count", 10000, function(error, bals) { + client.hscan(coin + ':balances', 0, "match", a+"*", "count", 50000, function(error, bals) { // get all payouts from address - client.hscan(coin + ':payouts', 0, "match", a+"*", "count", 10000, function(error, pays) { - + client.hscan(coin + ':payouts', 0, "match", a+"*", "count", 50000, function(error, pays) { + var workerName = ""; var balAmount = 0; var paidAmount = 0; var pendingAmount = 0; - + var workers = {}; - + for (var i in pays[1]) { if (Math.abs(i % 2) != 1) { workerName = String(pays[1][i]); @@ -329,11 +329,11 @@ module.exports = function(logger, portalConfig, poolConfigs){ workers[workerName] = (workers[workerName] || {}); } else { pendingAmount = parseFloat(pends[1][b]); - workers[workerName].immature = coinsRound(pendingAmount); + workers[workerName].immature = coinsRound(satoshisToCoins(pendingAmount)); totalImmature += pendingAmount; } } - + for (var w in workers) { balances.push({ worker:String(w), @@ -342,23 +342,95 @@ module.exports = function(logger, portalConfig, poolConfigs){ immature:workers[w].immature }); } - + pcb(); }); }); }); - }, function(err) { - if (err) { - callback("There was an error getting balances"); - return; - } - - _this.stats.balances = balances; - _this.stats.address = address; + }, function(err) { + if (err) { + callback("There was an error getting balances"); + return; + } - cback({totalHeld:coinsRound(totalHeld), totalPaid:coinsRound(totalPaid), totalImmature:satoshisToCoins(totalImmature), balances}); - }); - }; + _this.stats.balances = balances; + _this.stats.address = address; + + cback({totalHeld:coinsRound(totalHeld), totalPaid:coinsRound(totalPaid), totalImmature:satoshisToCoins(totalImmature), balances}); + }); + }; + + this.getPoolBalancesByAddress = function(address, callback){ + var a = address.split(".")[0]; + + var client = redisClients[0].client, + coins = redisClients[0].coins, + poolBalances = []; + + async.each(_this.stats.pools, function(pool, pcb) { + var poolName = pool.name; + var coin = String(poolName); + + // get all immature balances from address + client.hscan(coin + ':immature', 0, "match", a + "*", "count", 50000, function(error, pends) { + // get all balances from address + client.hscan(coin + ':balances', 0, "match", a + "*", "count", 50000, function(error, bals) { + // get all payouts from address + client.hscan(coin + ':payouts', 0, "match", a + "*", "count", 50000, function(error, pays) { + + var workers = {}; + + // Process payouts + for (var i = 0; i < pays[1].length; i += 2) { + var workerName = String(pays[1][i]); + var paidAmount = parseFloat(pays[1][i + 1]); + + workers[workerName] = workers[workerName] || {}; + workers[workerName].paid = coinsRound(paidAmount); + } + + // Process balances + for (var j = 0; j < bals[1].length; j += 2) { + var workerName = String(bals[1][j]); + var balAmount = parseFloat(bals[1][j + 1]); + + workers[workerName] = workers[workerName] || {}; + workers[workerName].balance = coinsRound(balAmount); + } + + // Process immature balances + for (var k = 0; k < pends[1].length; k += 2) { + var workerName = String(pends[1][k]); + var pendingAmount = parseFloat(pends[1][k + 1]); + + workers[workerName] = workers[workerName] || {}; + workers[workerName].immature = coinsRound(pendingAmount); + } + + // Push balances for each worker to the poolBalances array + for (var worker in workers) { + poolBalances.push({ + pool: poolName, + worker: worker, + balance: workers[worker].balance || 0, + paid: workers[worker].paid || 0, + immature: workers[worker].immature || 0 + }); + } + + pcb(); + }); + }); + }); + }, function(err) { + if (err) { + callback("There was an error getting balances"); + return; + } + + callback(poolBalances); + }); + }; this.getGlobalStats = function(callback){ diff --git a/libs/website.js b/libs/website.js index c64882194..e38e99fc1 100644 --- a/libs/website.js +++ b/libs/website.js @@ -134,7 +134,7 @@ module.exports = function(logger){ var buildKeyScriptPage = function(){ async.waterfall([ function(callback){ - var client = CreateRedisClient(portalConfig); + var client = CreateRedisClient(portalConfig.redis); if (portalConfig.redis.password) { client.auth(portalConfig.redis.password); } diff --git a/package-lock.json b/package-lock.json index 72f3f6490..748e8bb1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -440,7 +440,7 @@ "bigi": "^1.4.0", "bip66": "^1.1.0", "bitcoin-ops": "^1.3.0", - "blake2b": "git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac", + "blake2b": "git+https://github.com/Oink70/blake2b.git#3eaf87bc21da3eebc10a6e183bc237f1f44de9b0", "bs58check": "^2.0.0", "create-hash": "^1.1.0", "create-hmac": "^1.1.3", @@ -475,16 +475,16 @@ } }, "blake2b": { - "version": "git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac", - "from": "git+https://github.com/BitGo/blake2b.git#6268e6dd678661e0acc4359e9171b97eb1ebf8ac", + "version": "git+https://github.com/Oink70/blake2b.git#3eaf87bc21da3eebc10a6e183bc237f1f44de9b0", + "from": "git+https://github.com/Oink70/blake2b.git#3eaf87bc21da3eebc10a6e183bc237f1f44de9b0", "requires": { - "blake2b-wasm": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b", + "blake2b-wasm": "git+https://github.com/Oink70/blake2b-wasm.git#c183fabcb22cf84d463c7b10f25877a8e4afdc6d", "nanoassert": "^1.0.0" } }, "blake2b-wasm": { - "version": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b", - "from": "git+https://github.com/BitGo/blake2b-wasm.git#193cdb71656c1a6c7f89b05d0327bb9b758d071b", + "version": "git+https://github.com/Oink70/blake2b-wasm.git#c183fabcb22cf84d463c7b10f25877a8e4afdc6d", + "from": "git+https://github.com/Oink70/blake2b-wasm.git#c183fabcb22cf84d463c7b10f25877a8e4afdc6d", "requires": { "nanoassert": "^1.0.0" } @@ -1020,11 +1020,6 @@ "resolved": "https://registry.npmjs.org/dot/-/dot-1.1.2.tgz", "integrity": "sha1-xzdwGfxOVQeYkosrmv62ar+h8vk=" }, - "double-ended-queue": { - "version": "2.1.0-0", - "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1071,8 +1066,8 @@ } }, "equihashverify": { - "version": "git+https://github.com/s-nomp/equihashverify.git#2e9ca0742220c405be71efa2468bcfda0a5075f9", - "from": "git+https://github.com/s-nomp/equihashverify.git", + "version": "git+https://github.com/Oink70/equihashverify.git#2b2c5c9725f1e85a4eaa8b1c8f974aad65362c8e", + "from": "git+https://github.com/Oink70/equihashverify.git", "requires": { "bindings": "*", "libsodium": "^0.7.3", @@ -3084,24 +3079,35 @@ } }, "redis": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", "requires": { - "double-ended-queue": "^2.1.0-0", - "redis-commands": "^1.2.0", - "redis-parser": "^2.6.0" + "denque": "^1.5.0", + "redis-commands": "^1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" } }, + "denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, "redis-commands": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" }, "redis-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", - "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==" }, "regex-not": { "version": "1.0.2", @@ -3524,17 +3530,17 @@ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "stratum-pool": { - "version": "git+https://github.com/miketout/node-stratum-pool.git#c6e35910eb67a98f2315cb0d6a2d1b5eda639b85", - "from": "git+https://github.com/miketout/node-stratum-pool.git", + "version": "git+https://github.com/VerusCoin/node-stratum-pool.git#5a45e655b8d170253e1fbd56efbf85dd26d8347b", + "from": "git+https://github.com/VerusCoin/node-stratum-pool.git", "requires": { "async": "^2.6.1", "base58-native": "^0.1.4", "bignum": "^0.13.0", "bitgo-utxo-lib": "git+https://github.com/miketout/bitgo-utxo-lib.git#4649a79ffc55891e4ad40ac68793befa7348ee6a", - "equihashverify": "git+https://github.com/s-nomp/equihashverify.git#2e9ca0742220c405be71efa2468bcfda0a5075f9", + "equihashverify": "git+https://github.com/Oink70/equihashverify.git#2b2c5c9725f1e85a4eaa8b1c8f974aad65362c8e", "merkle-bitcoin": "^1.0.2", "promise": "^8.0.1", - "verushash": "git+https://github.com/VerusCoin/verushash-node.git#e6572f345b8c24e4a1212f6c9eb66c1c400d83e1" + "verushash": "git+https://github.com/VerusCoin/verushash-node.git#fd4d5fb9e7eefa34d70277bd5cc672bc472f821c" } }, "string-width": { @@ -3849,7 +3855,7 @@ } }, "verushash": { - "version": "git+https://github.com/VerusCoin/verushash-node.git#e6572f345b8c24e4a1212f6c9eb66c1c400d83e1", + "version": "git+https://github.com/VerusCoin/verushash-node.git#fd4d5fb9e7eefa34d70277bd5cc672bc472f821c", "from": "git+https://github.com/VerusCoin/verushash-node.git", "requires": { "bindings": "*", @@ -3945,6 +3951,35 @@ "argparse": "^1.0.7", "glob": "^7.0.5" } + }, + "wallet-address-validator": { + "version": "git+https://github.com/Oink70/wallet-address-validator.git#cf7835d1a569eed6fa98dd3d4aa4cd287bb59623", + "from": "git+https://github.com/Oink70/wallet-address-validator.git#cf7835d1a569eed6fa98dd3d4aa4cd287bb59623", + "dependencies": { + "base-x": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.4.tgz", + "integrity": "sha512-UYOadoSIkEI/VrRGSG6qp93rp2WdokiAiNYDfGW5qURAY8GiAQkvMbwNNSDYiVJopqv4gCna7xqf4rrNGp+5AA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "jssha": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-2.3.1.tgz", + "integrity": "sha1-FHshJTaQNcpLL30hDcU58Amz3po=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "jssha": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-2.3.1.tgz", + "integrity": "sha1-FHshJTaQNcpLL30hDcU58Amz3po=" } } } diff --git a/package.json b/package.json index 947142974..332e5efb5 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "dependencies": { "async": "^2.6.1", "bignum": "^0.13.0", - "bitgo-utxo-lib": "git+https://github.com/VerusCoin/bitgo-utxo-lib.git", + "bitgo-utxo-lib": "git+https://github.com/Oink70/bitgo-utxo-lib.git", "body-parser": "^1.18.3", "colors": "^1.3.2", "compression": "^1.7.3", @@ -48,12 +48,14 @@ "node-watch": "^0.5.8", "nonce": "^1.0.4", "pm2": "^3.2.2", - "redis": "^2.8.0", + "redis": "^3.0.0", "request": "^2.88.0", - "stratum-pool": "git+https://github.com/VerusCoin/node-stratum-pool.git" + "stratum-pool": "git+https://github.com/Oink70/node-stratum-pool.git", + "wallet-address-validator": "git+https://github.com/Oink70/wallet-address-validator.git#cf7835d1a569eed6fa98dd3d4aa4cd287bb59623", + "jssha": "^2.3.1" }, "engines": { - "node": ">=8.11" + "node": "10" }, "scripts": { "start": "NODE_PATH=$NODE_PATH:$PWD/node_modules/verushash/build/Release/:$PWD/node_modules/stratum-pool/node_modules/verushash/build/Release/ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/node_modules/stratum-pool/node_modules/equihashverify/build/Release/:$PWD/node_modules/equihashverify/build/Release/:$PWD/node_modules/verushash/build/Release/:$PWD/node_modules/stratum-pool/node_modules/verushash/build/Release/ node init.js" diff --git a/pool_configs/examples/vrsc.json b/pool_configs/examples/vrsc.json index af3fd906b..99f34c046 100644 --- a/pool_configs/examples/vrsc.json +++ b/pool_configs/examples/vrsc.json @@ -35,7 +35,7 @@ "paymentMode": "prop", "_comment_paymentMode":"prop, pplnt", "pplnt": 51, - "_comment_pplnt": "If pplnt is active and clients have mined less that this part, their shares are slashed." + "_comment_pplnt": "If pplnt is active and clients have mined less that this part, their shares are slashed.", "paymentInterval": 120, "_comment_paymentInterval": "Interval in seconds to check and perform payments.", "minimumPayment": 1, diff --git a/scripts/pbaascheck.sh b/scripts/pbaascheck.sh new file mode 100755 index 000000000..351ac7356 --- /dev/null +++ b/scripts/pbaascheck.sh @@ -0,0 +1,276 @@ +#!/bin/bash +## +## © verus.io 2018-2024, released under MIT license +## Script written in 2023 by Oink.vrsc@ +## Script maintained by Oink.vrsc@ + +# check if script is already running +if [ -f /tmp/pbaascheck.pid ] +then + PID_TIME=$(stat -c '%W' /tmp/pbaascheck.pid) + CUR_TIME=$(date +%s) + PID_AGE=$(echo "$CUR_TIME - $PID_TIME" | bc) + if [[ $PID_AGE -le 3600 ]] + then + echo "script is already running" + exit 1 + else + echo "script has apparently aborted before removing /tmp/pbaascheck.pid, continuing" + fi +else + touch /tmp/pbaascheck.pid +fi + +## default settings (change where needed) +PAYMENT=/home/pool/payment # Change if you used a different location +VERUS=/home/verus/bin/verus # complete path to (and including) the verus RPC client +MAIN_CHAIN=VRSC # main hashing chain +REDIS_NAME=verus # name you assigned the coin in `/home/pool/s-nomp/coins/*.json` +REDIS_HOST=127.0.0.1 # If you run this script on another system, alter the IP address of your Redis server +REDIS_PORT=6379 # If you use a different REDIS port, alter the port accordingly + +## Set script folder +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" + +## check if the Verus binary is found. +## If Verus exists in the PATH environment, use it. +## If not, fall back to predefined location in this script. +if ! command -v verus &>/dev/null +then + echo "verus not found in your PATH environment. Using location from line 9 in this script." + if ! command -v $VERUS &>/dev/null + then + echo "Verus could not be found. Make sure it's in your path and/or in line 9 of this script." + echo "exiting..." + exit 1 + fi +else + VERUS=$(which verus) +fi + + +## Dependencies: bc, jq, tr, cut, redis-cli/keydb-cli +## bc +if ! command -v bc &>/dev/null ; then + echo "jq not found. please install using your package manager." + exit 1 +else + BC=$(which bc) +fi +## jq +if ! command -v jq &>/dev/null ; then + echo "jq not found. please install using your package manager." + exit 1 +else + JQ=$(which jq) +fi +## tr +if ! command -v tr &>/dev/null ; then + echo "tr not found. please install using your package manager." + exit 1 +else + TR=$(which tr) +fi +## cut +if ! command -v cut &>/dev/null ; then + echo "cut not found. please install using your package manager." + exit 1 +else + CUT=$(which cut) +fi +## redis-cli and/or keydb-cli +if ! command -v redis-cli &>/dev/null ; then + if ! command -v keydb-cli &>/dev/null ; then + echo "Both redis-cli or keydb-cli not found. Please install one using your package manager." + exit 1 + fi + REDIS_CLI="$(which keydb-cli) -h $REDIS_HOST -p $REDIS_PORT" +else + REDIS_CLI="$(which redis-cli) -h $REDIS_HOST -p $REDIS_PORT" +fi + +## Can we connect to Redis? +if [[ "$($REDIS_CLI ping)" != "PONG" ]] +then + echo "cannot connect to redis server" + exit 1 +fi + +## Is main chain active? +count=$(${VERUS} -chain=$MAIN_CHAIN getconnectioncount 2>/dev/null) +case $count in + ''|*[!0-9]*) DAEMON_ACTIVE=0 ;; + *) DAEMON_ACTIVE=1 ;; +esac +if [[ "$DAEMON_ACTIVE" != "1" ]] +then + echo "$MAIN_CHAIN daemon is not running and connected. Start your $MAIN_CHAIN daemon and wait for it to be connected." + exit 1 +fi + +## Return a list of found PBaaS hashes: +HASHLIST=$($REDIS_CLI smembers $REDIS_NAME:pbaasPending | $CUT -d' ' -f2-) +## return a list on found shares per miner +SHARELIST=$($REDIS_CLI hgetall $REDIS_NAME:shares:pbaasCurrent| $CUT -d' ' -f2-) +## get list of active chains in ecosystem +PBAAS_CHAINS=$($VERUS -chain=$MAIN_CHAIN listcurrencies '{"systemtype":"pbaas"}' | jq --arg MAIN_CHAIN "${MAIN_CHAIN}" -r '.[].currencydefinition | select (.name != "$MAIN_CHAIN") | .name') +## determine chains active on this system +for i in $PBAAS_CHAINS +do + count=$(${VERUS} -chain=$i getconnectioncount 2>/dev/null) + case $count in + ''|*[!0-9]*) DAEMON_ACTIVE=0 ;; + *) DAEMON_ACTIVE=1 ;; + esac + if [[ "$DAEMON_ACTIVE" = "1" ]] + then + ACTIVE_CHAINS="$ACTIVE_CHAINS $i" + fi +done +ACTIVE_CHAINS=$(echo $ACTIVE_CHAINS | sed 's/VRSC//g'); + +## Check each hash on all chains +for i in $HASHLIST +do + for j in $ACTIVE_CHAINS + ## put in break for non-running chains + do + CHECK=$($VERUS -chain=$j getblock $(echo $i | $CUT -d':' -f1) 2 2>/dev/null) + if [[ "$CHECK" =~ "$(echo $i | cut -d':' -f1)" ]] + then + TRANSACTION=$(echo "$CHECK" | $JQ -r '.tx[0].txid') + BLOCK=$(echo "$CHECK" | $JQ '.height') + echo "$j contains blockhash $(echo $i | cut -d':' -f1), TXID: $TRANSACTION" + REDIS_NEW_PENDING="${i:0:65}"$TRANSACTION:$BLOCK:"${i:65}" + # do not insert data for the main mining chain + if [[ "$(echo $j | $TR '[:upper:]' '[:lower:]')" != "$(echo $MAIN_CHAIN | $TR '[:upper:]' '[:lower:]')" ]] + then + $REDIS_CLI sadd $(echo $j | $TR '[:upper:]' '[:lower:]'):blocksPending $REDIS_NEW_PENDING 1>/dev/null + ## if no shares are known for this round yet, add them + SHARES_AVAILABLE="$($REDIS_CLI hgetall $(echo $j | tr '[:upper:]' '[:lower:]'):shares:round$BLOCK)" + if [[ "$SHARES_AVAILABLE" == "" ]] + then + $REDIS_CLI hset $(echo $j | tr '[:upper:]' '[:lower:]'):shares:round$BLOCK $SHARELIST 1>/dev/null + fi + fi + fi + done +done + +# Temporarily store unknown blockhashes in the script folder +# ToDo: Needs storing to Redis +UNKNOWN_HASHLIST=$($REDIS_CLI smembers $REDIS_NAME:pbaasPending | $CUT -d' ' -f2-) +if [ -f $SCRIPT_DIR/unknown_hashlist.4 ] +then + while read -r LINE + do + if [[ "$UNKNOWN_HASHLIST" == *"$LINE"* ]] + then + echo "removing $LINE from REDIS" + $REDIS_CLI srem $REDIS_NAME:pbaasPending $LINE 1>/dev/null + fi + done < $SCRIPT_DIR/unknown_hashlist.4 + rm $SCRIPT_DIR/unknown_hashlist.4 +fi + +for i in {3..1} +do + if [ -f $SCRIPT_DIR/unknown_hashlist.$i ] + then + mv $SCRIPT_DIR/unknown_hashlist.$i $SCRIPT_DIR/unknown_hashlist.$((i+1)) + fi +done + +for i in $UNKNOWN_HASHLIST +do + echo $i >> $SCRIPT_DIR/unknown_hashlist.1 +done + +for CHAIN in $ACTIVE_CHAINS +do + CHAINlc=$(echo $(echo $CHAIN | $TR '[:upper:]' '[:lower:]')) + INVALIDADDRESS=$(cat $PAYMENT/pool_configs/$CHAINlc.json | jq -r .invalidAddress) + echo "Processing i-addresses on $CHAIN. $INVALIDADDRESS used for nonexisting IDs" + ALL_ADDRESSES=$($REDIS_CLI HSCAN $CHAINlc:balances 0 COUNT 50000 | awk '{print $1}' | sed -n 'n;p' | sed 's/\..*//' | grep -e "^i.*" | sort | uniq) + while read -r ADDRESS; do + if [[ $ADDRESS == i* ]] + then + I_ADDRESS=$ADDRESS + BALANCES= + DONATIONS= + if [[ $($VERUS -chain=$CHAIN getidentity "$I_ADDRESS") ]] + then + echo "$I_ADDRESS exists on vARRR, no action needed." + else + ## Retrieve ID info from mainchain + ID_MAINCHAIN=$($VERUS getidentity "$I_ADDRESS") + # check if the ID exists on the main chain + if [[ $(echo $IDMAINCHAIN) == error* ]] + then + break # testing WTF is happening. + # Collect all address.worker entries for the i-address for move to donation address + echo "$I_ADDRESS balance is a donation on $CHAIN..." + DONATION_TMP=$($REDIS_CLI hscan $CHAINlc:balances 0 COUNT 40000 MATCH $ADDRESS* | awk 'NR % 2 == 0') + if ! [ "$DONATION_TMP" == "" ] + then + DONATIONS=$DONATIONS$DONATION_TMP" " + while read OLD_ADDRESS + do + BALANCE=0 + NEW_ADDRESS=0 + tmp=(${OLD_ADDRESS//./ }) + addr=${tmp[0]} + NEW_ADDRESS=$(echo $OLD_ADDRESS | sed "s/${addr}/${INVALIDADDRESS}/g") + BALANCE=$($REDIS_CLI HGET $CHAINlc:balances $OLD_ADDRESS) + $REDIS_CLI HDEL $CHAINlc:balances $OLD_ADDRESS + $REDIS_CLI HINCBYFLOAT $CHAINlc:balances $NEW_ADDRESS $BALANCE + done <<<$DONATIONS + fi + else + # Collect all address.worker entries for the i-address for move to R-address + BALANCES_TMP=$($REDIS_CLI hscan $CHAINlc:balances 0 COUNT 40000 MATCH $ADDRESS* | awk 'NR % 2 == 0') + if ! [ "$BALANCES_TMP" == "" ] + then + BALANCES=$BALANCES$BALANCES_TMP" " + R_ADDRESS=$(echo $ID_MAINCHAIN | jq -r .identity.primaryaddresses[0]) + while read OLD_ADDRESS + do + tmp=(${OLD_ADDRESS//./ }) + addr=${tmp[0]} + NEW_ADDRESS=$(echo $OLD_ADDRESS | sed "s/${addr}/${R_ADDRESS}/g") + BALANCE=$($REDIS_CLI HGET $CHAINlc:balances $OLD_ADDRESS) + $REDIS_CLI HDEL $CHAINlc:balances $OLD_ADDRESS + $REDIS_CLI HINCRBYFLOAT $CHAINlc:balances $NEW_ADDRESS $BALANCE + done <<<$BALANCES + fi + fi + fi + fi + done<<<$ALL_ADDRESSES +done + +WORKERSHAREREDUCTION= +## Retrieve data from REDIS +WORKERSHARES=$($REDIS_CLI HGETALL $REDIS_NAME:shares:pbaasCurrent) + +## if the value of shares is below two, remove the key from the database, otherwise substract 50% of the value +for LINE in $WORKERSHARES +do + if [[ "$LINE" =~ ^[0-9] ]] + then + if [[ "$LINE" < "2.00000000" ]] + then + $REDIS_CLI HDEL $REDIS_NAME:shares:pbaasCurrent "$WORKERSHAREREDUCTION" + else + REDUCTION=$(echo "scale=8;$LINE / 2" | $BC) + $REDIS_CLI HINCRBYFLOAT $REDIS_NAME:shares:pbaasCurrent "$WORKERSHAREREDUCTION" "-$REDUCTION" + fi + WORKERSHAREREDUCTION= + else + WORKERSHAREREDUCTION=$LINE + fi +done <<<$WORKERSHARES + +rm /tmp/pbaascheck.pid + +#EOF diff --git a/website/pages/api.html b/website/pages/api.html index 1fdb5889a..719bc9fa9 100644 --- a/website/pages/api.html +++ b/website/pages/api.html @@ -7,7 +7,9 @@
  • /pool_stats - historical stats
  • /payments - payment history
  • /worker_stats?taddr - historical time per pool json
  • +
  • /worker_balances?taddr - balance per pool json
  • /live_stats - live stats
  • + diff --git a/website/pages/miner_stats.html b/website/pages/miner_stats.html index 5349f9b97..0fd65c13b 100644 --- a/website/pages/miner_stats.html +++ b/website/pages/miner_stats.html @@ -1,9 +1,9 @@
    - -
    {{=String(it.stats.address).split(".")[0]}}
    -
    ... (Avg)
    -
    ... (Now)
    -
    Luck ... Days
    -
    -
    -
    -
    Shares: ...
    -
    Immature: ...
    -
    Bal: ...
    -
    Paid: ...
    -
    + +
    {{=String(it.stats.address).split(".")[0]}}
    +
    ... (Avg)
    +
    ... (Now)
    +
    Luck ... Days
    +
    +
    +
    +
    Shares: ...
    +
    +
    + + +
    +
    +
    +
    Pool Balances

    +
    +
    + +
    diff --git a/website/pages/payments.html b/website/pages/payments.html index 579d2f17a..bab582f61 100644 --- a/website/pages/payments.html +++ b/website/pages/payments.html @@ -66,6 +66,7 @@ {{ for(var p in it.stats.pools[pool].payments) { }} +

    {{=it.stats.pools[pool].symbol}} payments

    {{ if (it.poolsConfigs[pool].coin.explorer && it.poolsConfigs[pool].coin.explorer.txURL) { }} @@ -74,7 +75,8 @@ {{=it.stats.pools[pool].payments[p].blocks}} {{ } }} - {{=readableDate(it.stats.pools[pool].payments[p].time)}} + + {{=it.stats.pools[pool].payments[p].miners}} {{=Math.round(it.stats.pools[pool].payments[p].shares)}} {{=it.stats.pools[pool].payments[p].paid}} {{=it.stats.pools[pool].symbol}} diff --git a/website/pages/stats.html b/website/pages/stats.html index a0f92eea6..3683b6b69 100644 --- a/website/pages/stats.html +++ b/website/pages/stats.html @@ -149,7 +149,9 @@ {{=block[2]}} {{ } }} {{if (block[4] != null) { }} - {{=readableDate(block[4])}} + + + {{ } }} {{if (it.stats.pools[pool].pending.confirms) { }} {{if (it.stats.pools[pool].pending.confirms[block[0]]) { }} @@ -175,7 +177,9 @@ {{=block[2]}} {{ } }} {{if (block[4] != null) { }} - {{=readableDate(block[4])}} + + + {{ } }} *CREDITED*
    Mined By: {{=block[3]}}
    diff --git a/website/pages/workers.html b/website/pages/workers.html index 27f6378f4..f28fd68d3 100644 --- a/website/pages/workers.html +++ b/website/pages/workers.html @@ -52,43 +52,45 @@ {{ function capitalizeFirstLetter(t){return t.charAt(0).toUpperCase()+t.slice(1)} }} {{ var i=0; for(var pool in it.stats.pools) { }} -
    -
    -
    - - Miner Lookup: - - - - - - - {{=capitalizeFirstLetter(it.stats.pools[pool].name)}} Top Miners    - {{=it.stats.pools[pool].minerCount}} Miners    - {{=it.stats.pools[pool].workerCount}} Workers    - {{=it.stats.pools[pool].shareCount}} Shares -
    -
    - - - - - - - - - - {{ for(var worker in it.stats.pools[pool].miners) { }} - {{var workerstat = it.stats.pools[pool].miners[worker];}} - - - - - +{{ if (it.stats.pools[pool].minerCount > 0) { }} +
    +
    +
    + + Miner Lookup: + + + + + + + {{=capitalizeFirstLetter(it.stats.pools[pool].name)}} Top Miners    + {{=it.stats.pools[pool].minerCount}} Miners    + {{=it.stats.pools[pool].workerCount}} Workers    + {{=it.stats.pools[pool].shareCount}} Shares +
    +
    +
    AddressSharesEfficiencyHashrate
    {{=worker}}{{=Math.round(workerstat.currRoundShares * 100) / 100}}{{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}}{{=workerstat.hashrateString}}
    + + + + + + - {{ } }} -
    AddressSharesEfficiencyHashrate
    + + {{ for(var worker in it.stats.pools[pool].miners) { }} + {{var workerstat = it.stats.pools[pool].miners[worker];}} + + {{=worker}} + {{=Math.round(workerstat.currRoundShares * 100) / 100}} + {{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}} + {{=workerstat.hashrateString}} + + {{ } }} + +
    - +{{ } }} {{ } }}