From 73668709ce2a73c36cb87b318bc2cc30bfef4d41 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 2 May 2014 15:59:46 -0600 Subject: [PATCH 01/46] Added rewardRecipients configuration, refactored payment processing, moved p2p magic to coin config --- README.md | 166 ++++--- coins/litecoin.json | 4 +- init.js | 9 +- libs/mposCompatibility.js | 40 +- libs/paymentProcessor.js | 698 +++++++++++------------------ libs/poolWorker.js | 100 ++--- libs/profitSwitch.js | 2 +- libs/shareProcessor.js | 7 +- libs/stats.js | 15 +- libs/website.js | 2 +- pool_configs/litecoin_example.json | 82 ++-- 11 files changed, 450 insertions(+), 675 deletions(-) diff --git a/README.md b/README.md index eeb7214..85333f5 100644 --- a/README.md +++ b/README.md @@ -284,13 +284,22 @@ Here is an example of the required fields: { "name": "Litecoin", "symbol": "ltc", - "algorithm": "scrypt", //or "sha256", "scrypt-jane", "scrypt-n", "quark", "x11" - "txMessages": false, //or true (not required, defaults to false) - "mposDiffMultiplier": 256, //only for x11 coins in mpos mode, set to 256 (optional) + "algorithm": "scrypt", + + /* Magic value only required for setting up p2p block notifications. It is found in the daemon + source code as the pchMessageStart variable. + For example, litecoin mainnet magic: http://git.io/Bi8YFw + And for litecoin testnet magic: http://git.io/NXBYJA */ + "peerMagic": "fbc0b6db" //optional + "peerMagicTestnet": "fcc1b7dc" //optional + + //"txMessages": false, //options - defaults to false + + //"mposDiffMultiplier": 256, //options - only for x11 coins in mpos mode } ```` -For additional documentation how to configure coins *(especially important for scrypt-n and scrypt-jane coins)* +For additional documentation how to configure coins and their different algorithms see [these instructions](//github.com/zone117x/node-stratum-pool#module-usage). @@ -307,6 +316,17 @@ Description of options: "address": "mi4iBXbBsydtcc5yFmsff2zCFVX4XG7qJc", //Address to where block rewards are given + /* Block rewards go to the configured pool wallet address to later be paid out to miners, + except for a percentages that can go to pool operator(s) as pool fees or donations. + Addresses or hashed public keys can be used. */ + "rewardRecipients": { + "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op + "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, //0.5% goes to a pool co-owner + + //0.1% donation to NOMP to help support development + "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 + }, + "blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds /* How many milliseconds should have passed before new block transactions will trigger a new @@ -332,96 +352,68 @@ Description of options: miners/pools that deal with scrypt use a guesstimated one that is about 5.86% off from the actual one. So here we can set a tolerable threshold for if a share is slightly too low due to mining apps using incorrect max diffs and this pool using correct max diffs. */ - "shareVariancePercent": 10, + "shareVariancePercent": 2, /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy protocol enabled, such as HAProxy with 'send-proxy' param: http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ "tcpProxyProtocol": false, + /* To receive payments, miners must connect with their address or mining key as their username. + This option will only authenticate miners using an address or mining key. */ + "validateWorkerAddress": true, - /* This determines what to do with submitted shares (and stratum worker authentication). - You have two options: - 1) Enable internal and disable mpos = this portal to handle all share payments. - 2) Enable mpos and disable internal = shares will be inserted into MySQL database - for MPOS to process. */ - "shareProcessing": { + "paymentProcessing": { + "enabled": true, - "internal": { - "enabled": true, + /* Every this many seconds get submitted blocks from redis, use daemon RPC to check + their confirmation status, if confirmed then get shares from redis that contributed + to block and send out payments. */ + "paymentInterval": 30, - /* When workers connect, to receive payments, their address must be used as the worker - name. If this option is true, on worker authentication, their address will be - verified via a validateaddress API call to the daemon. Miners with invalid addresses - will be rejected. */ - "validateWorkerAddress": true, + /* Minimum number of coins that a miner must earn before sending payment. Typically, + a higher minimum means less transactions fees (you profit more) but miners see + payments less frequently (they dislike). Opposite for a lower minimum payment. */ + "minimumPayment": 0.01, - /* Every this many seconds get submitted blocks from redis, use daemon RPC to check - their confirmation status, if confirmed then get shares from redis that contributed - to block and send out payments. */ - "paymentInterval": 30, - - /* Minimum number of coins that a miner must earn before sending payment. Typically, - a higher minimum means less transactions fees (you profit more) but miners see - payments less frequently (they dislike). Opposite for a lower minimum payment. */ - "minimumPayment": 0.001, - - /* Minimum number of coins to keep in pool wallet. It is recommended to deposit at - at least this many coins into the pool wallet when first starting the pool. */ - "minimumReserve": 10, - - /* (2% default) What percent fee your pool takes from the block reward. */ - "feePercent": 0.02, - - /* Name of the daemon account to use when moving coin profit within daemon wallet. */ - "feeCollectAccount": "feesCollected", - - /* Your address that receives pool revenue from fees. */ - "feeReceiveAddress": "LZz44iyF4zLCXJTU8RxztyyJZBntdS6fvv", - - /* How many coins from fee revenue must accumulate on top of the - minimum reserve amount in order to trigger withdrawal to fee address. The higher - this threshold, the less of your profit goes to transactions fees. */ - "feeWithdrawalThreshold": 5, - - /* This daemon is used to send out payments. It MUST be for the daemon that owns the - configured 'address' that receives the block rewards, otherwise the daemon will not - be able to confirm blocks or send out payments. */ - "daemon": { - "host": "127.0.0.1", - "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - - /* Redis database used for storing share and block submission data. */ - "redis": { - "host": "127.0.0.1", - "port": 6379 - } - }, - - /* Enabled mpos and shares will be inserted into share table in a MySQL database. You may - also want to use the "emitInvalidBlockHashes" option below if you require it. */ - "mpos": { - "enabled": false, - "host": "127.0.0.1", //MySQL db host - "port": 3306, //MySQL db port - "user": "me", //MySQL db user - "password": "mypass", //MySQL db password - "database": "ltc", //MySQL db database name - - /* Unregistered workers can automatically be registered (added to database) on stratum - worker authentication if this is true. */ - "autoCreateWorker": false, - - /* For when miner's authenticate: set to "password" for both worker name and password to - be checked for in the database, set to "worker" for only work name to be checked, or - don't use this option (set to "none") for no auth checks */ - "stratumAuth": "password" + /* This daemon is used to send out payments. It MUST be for the daemon that owns the + configured 'address' that receives the block rewards, otherwise the daemon will not + be able to confirm blocks or send out payments. */ + "daemon": { + "host": "127.0.0.1", + "port": 19332, + "user": "litecoinrpc", + "password": "testnet" } }, + /* Redis database used for storing share and block submission data and payment processing. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + + /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want + to use the "emitInvalidBlockHashes" option below if you require it. The config options + "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ + "mposMode": { + "enabled": false, + "host": "127.0.0.1", //MySQL db host + "port": 3306, //MySQL db port + "user": "me", //MySQL db user + "password": "mypass", //MySQL db password + "database": "ltc", //MySQL db database name + + /* Checks for valid password in database when miners connect. */ + "checkPassword": true, + + /* Unregistered workers can automatically be registered (added to database) on stratum + worker authentication if this is true. */ + "autoCreateWorker": false + + + }, + /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP to reduce system/network load. Also useful to fight against flooding attacks. If running behind something like HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up @@ -476,8 +468,8 @@ Description of options: /* This allows the pool to connect to the daemon as a node peer to receive block updates. It may be the most efficient way to get block updates (faster than polling, less - intensive than blocknotify script). It requires additional setup: the 'magic' field must - be exact (extracted from the coin source code). */ + intensive than blocknotify script). It requires the additional field "peerMagic" in + the coin config. */ "p2p": { "enabled": false, @@ -490,13 +482,7 @@ Description of options: /* If your coin daemon is new enough (i.e. not a shitcoin) then it will support a p2p feature that prevents the daemon from spamming our peer node with unnecessary transaction data. Assume its supported but if you have problems try disabling it. */ - "disableTransactions": true, - - /* Magic value is different for main/testnet and for each coin. It is found in the daemon - source code as the pchMessageStart variable. - For example, litecoin mainnet magic: http://git.io/Bi8YFw - And for litecoin testnet magic: http://git.io/NXBYJA */ - "magic": "fcc1b7dc" + "disableTransactions": true } } diff --git a/coins/litecoin.json b/coins/litecoin.json index 73307fb..588af55 100644 --- a/coins/litecoin.json +++ b/coins/litecoin.json @@ -1,5 +1,7 @@ { "name": "Litecoin", "symbol": "LTC", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "fcc1b7dc" } \ No newline at end of file diff --git a/init.js b/init.js index bf5c0c9..aacb92e 100644 --- a/init.js +++ b/init.js @@ -161,13 +161,6 @@ var spawnPoolWorkers = function(){ Object.keys(poolConfigs).forEach(function(coin){ var p = poolConfigs[coin]; - var internalEnabled = p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled; - var mposEnabled = p.shareProcessing && p.shareProcessing.mpos && p.shareProcessing.mpos.enabled; - - if (!internalEnabled && !mposEnabled){ - logger.error('Master', coin, 'Share processing is not configured so a pool cannot be started for this coin.'); - delete poolConfigs[coin]; - } if (!Array.isArray(p.daemons) || p.daemons.length < 1){ logger.error('Master', coin, 'No daemons configured so a pool cannot be started for this coin.'); @@ -364,7 +357,7 @@ var startPaymentProcessor = function(){ var enabledForAny = false; for (var pool in poolConfigs){ var p = poolConfigs[pool]; - var enabled = p.enabled && p.shareProcessing && p.shareProcessing.internal && p.shareProcessing.internal.enabled; + var enabled = p.enabled && p.paymentProcessing && p.paymentProcessing.enabled; if (enabled){ enabledForAny = true; break; diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 0a3b1c7..6f044c4 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -2,31 +2,30 @@ var mysql = require('mysql'); var cluster = require('cluster'); module.exports = function(logger, poolConfig){ - var mposConfig = poolConfig.shareProcessing.mpos; + var mposConfig = poolConfig.mposMode; var coin = poolConfig.coin.name; - var connection; + var connection = mysql.createPool({ + host: mposConfig.host, + port: mposConfig.port, + user: mposConfig.user, + password: mposConfig.password, + database: mposConfig.database + }); var logIdentify = 'MySQL'; var logComponent = coin; - function connect(){ - connection = mysql.createPool({ - host: mposConfig.host, - port: mposConfig.port, - user: mposConfig.user, - password: mposConfig.password, - database: mposConfig.database - }); - - - } - connect(); this.handleAuth = function(workerName, password, authCallback){ - + + if (poolConfig.validateWorkerUsername !== true && mposConfig.autoCreateWorker !== true){ + authCallback(true); + return; + } + connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', [workerName.toLowerCase()], @@ -65,16 +64,15 @@ module.exports = function(logger, poolConfig){ } } ); - }else{ + } + else{ authCallback(false); } } - else if (mposConfig.stratumAuth === 'worker') - authCallback(true); - else if (result[0].password === password) - authCallback(true) - else + else if (mposConfig.checkPassword && result[0].password !== password) authCallback(false); + else + authCallback(true); } ); diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 638c64d..d3f2b51 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -13,9 +13,8 @@ module.exports = function(logger){ Object.keys(poolConfigs).forEach(function(coin) { var poolOptions = poolConfigs[coin]; - if (poolOptions.shareProcessing && - poolOptions.shareProcessing.internal && - poolOptions.shareProcessing.internal.enabled) + if (poolOptions.paymentProcessing && + poolOptions.paymentProcessing.enabled) enabledPools.push(coin); }); @@ -27,14 +26,14 @@ module.exports = function(logger){ coins.forEach(function(coin){ var poolOptions = poolConfigs[coin]; - var processingConfig = poolOptions.shareProcessing.internal; + var processingConfig = poolOptions.paymentProcessing; var logSystem = 'Payments'; var logComponent = coin; logger.debug(logSystem, logComponent, 'Payment processing setup to run every ' + processingConfig.paymentInterval + ' second(s) with daemon (' + processingConfig.daemon.user + '@' + processingConfig.daemon.host + ':' + processingConfig.daemon.port - + ') and redis (' + processingConfig.redis.host + ':' + processingConfig.redis.port + ')'); + + ') and redis (' + poolOptions.redis.host + ':' + poolOptions.redis.port + ')'); }); }); @@ -45,68 +44,64 @@ function SetupForPool(logger, poolOptions, setupFinished){ var coin = poolOptions.coin.name; - var processingConfig = poolOptions.shareProcessing.internal; + var processingConfig = poolOptions.paymentProcessing; var logSystem = 'Payments'; var logComponent = coin; - var processingPayments = true; + var daemon = new Stratum.daemon.interface([processingConfig.daemon]); + var redisClient = redis.createClient(poolOptions.redis.port, poolOptions.redis.host); - var daemon; - var redisClient; + var magnitude; + var minPaymentSatoshis; + var coinPrecision; + + var paymentInterval; async.parallel([ - function(callback){ - daemon = new Stratum.daemon.interface([processingConfig.daemon]); - daemon.once('online', function(){ - daemon.cmd('validateaddress', [poolOptions.address], function(result){ - if (!result[0].response || !result[0].response.ismine){ - logger.error(logSystem, logComponent, + daemon.cmd('validateaddress', [poolOptions.address], function(result) { + if (result.error){ + logger.error(logSystem, logComponent, 'Error with payment processing daemon ' + JSON.stringify(result.error)); + callback(true); + } + else if (!result.response || !result.response.ismine) { + logger.error(logSystem, logComponent, 'Daemon does not own pool address - payment processing can not be done with this daemon, ' - + JSON.stringify(result[0].response)); - return; - } + + JSON.stringify(result.response)); + callback(true); + } + else{ callback() - }); - }).once('connectionFailed', function(error){ - logger.error(logSystem, logComponent, 'Failed to connect to daemon for payment processing: config ' + - JSON.stringify(processingConfig.daemon) + ', error: ' + - JSON.stringify(error)); - callback('Error connecting to deamon'); - }).on('error', function(error){ - logger.error(logSystem, logComponent, 'Daemon error ' + JSON.stringify(error)); - }).init(); + } + }, true); }, function(callback){ - - redisClient = redis.createClient(processingConfig.redis.port, processingConfig.redis.host); - redisClient.on('ready', function(){ - if (callback) { - callback(); - callback = null; + daemon.cmd('getbalance', [], function(result){ + if (result.error){ + callback(true); return; } - logger.debug(logSystem, logComponent, 'Connected to redis at ' - + processingConfig.redis.host + ':' + processingConfig.redis.port + ' for payment processing'); - }).on('end', function(){ - logger.error(logSystem, logComponent, 'Connection to redis database as been ended'); - }).once('error', function(){ - if (callback) { - logger.error(logSystem, logComponent, 'Failed to connect to redis at ' - + processingConfig.redis.host + ':' + processingConfig.redis.port + ' for payment processing'); - callback('Error connecting to redis'); - callback = null; + try { + var d = result.data.split('result":')[1].split(',')[0].split('.')[1]; + magnitude = parseInt('10' + new Array(d.length).join('0')); + minPaymentSatoshis = parseInt(processingConfig.minimumPayment * magnitude); + coinPrecision = magnitude.toString().length - 1; + callback(); + } + catch(e){ + logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing'); + callback(true); } - }); + }, true, true); } ], function(err){ if (err){ setupFinished(false); return; } - setInterval(function(){ + paymentInterval = setInterval(function(){ try { processPayments(); } catch(e){ @@ -118,97 +113,74 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - /* Call redis to check if previous sendmany and/or redis cleanout commands completed successfully. - If sendmany worked fine but redis commands failed you HAVE TO run redis commands again - (manually) to prevent double payments. If sendmany failed too you can safely delete - coin + '_finalRedisCommands' string from redis to let pool calculate payments again. */ - function checkPreviousPaymentsStatus(callback) { - redisClient.get(coin + '_finalRedisCommands', function(error, reply) { - if (error){ - callback('Could not get finalRedisCommands - ' + JSON.stringify(error)); - return; - } - if (reply) { - callback('Payments stopped because of the critical error - failed commands saved in ' - + coin + '_finalRedisCommands redis set:\n' + reply); - return; - } else { - /* There was no error in previous sendmany and/or redis cleanout commands - so we can safely continue */ - processingPayments = false; - callback(); - } - }); - } - /* Number.toFixed gives us the decimal places we want, but as a string. parseFloat turns it back into number - we don't care about trailing zeros in this case. */ - var toPrecision = function(value, precision){ - return parseFloat(value.toFixed(precision)); + var satoshisToCoins = function(satoshis){ + return parseFloat((satoshis / magnitude).toFixed(coinPrecision)); }; - /* Deal with numbers in smallest possible units (satoshis) as much as possible. This greatly helps with accuracy when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */ var processPayments = function(){ - var startPaymentProcess = Date.now(); - async.waterfall([ + var timeSpentRPC = 0; + var timeSpentRedis = 0; - function(callback) { - if (processingPayments) { - checkPreviousPaymentsStatus(function(error){ - if (error) { - logger.error(logSystem, logComponent, error); - callback('Check finished - previous payments processing error'); - return; - } - callback(); - }); - return; - } - callback(); - }, + var startTimeRedis; + var startTimeRPC; + + var startRedisTimer = function(){ startTimeRedis = Date.now() }; + var endRedisTimer = function(){ timeSpentRedis += Date.now() - startTimeRedis }; + + var startRPCTimer = function(){ startTimeRPC = Date.now(); }; + var endRPCTimer = function(){ timeSpentRPC += Date.now() - startTimeRedis }; + + async.waterfall([ /* Call redis to get an array of rounds - which are coinbase transactions and block heights from submitted blocks. */ function(callback){ - redisClient.smembers(coin + '_blocksPending', function(error, results){ + startRedisTimer(); + redisClient.multi([ + ['hgetall', coin + '_balances'], + ['smembers', coin + '_blocksPending'] + ]).exec(function(error, results){ + endRedisTimer(); if (error){ logger.error(logSystem, logComponent, 'Could not get blocks from redis ' + JSON.stringify(error)); - callback('Check finished - redis error for getting blocks'); - return; - } - if (results.length === 0){ - callback('Check finished - no pending blocks in redis'); + callback(true); return; } - var rounds = results.map(function(r){ + + + var workers = {}; + for (var w in results[0]){ + workers[w] = {balance: parseInt(results[0][w])}; + } + + var rounds = results[1].map(function(r){ var details = r.split(':'); return { - category: details[0].category, blockHash: details[0], txHash: details[1], height: details[2], - reward: details[3], serialized: r }; }); - callback(null, rounds); + callback(null, workers, rounds); }); }, /* Does a batch rpc call to daemon with all the transaction hashes to see if they are confirmed yet. It also adds the block reward amount to the round object - which the daemon gives also gives us. */ - function(rounds, callback){ + function(workers, rounds, callback){ var batchRPCcommand = rounds.map(function(r){ return ['gettransaction', [r.txHash]]; @@ -216,11 +188,14 @@ function SetupForPool(logger, poolOptions, setupFinished){ batchRPCcommand.push(['getaccount', [poolOptions.address]]); + startRPCTimer(); daemon.batchCmd(batchRPCcommand, function(error, txDetails){ + endRPCTimer(); if (error || !txDetails){ - callback('Check finished - daemon rpc error with batch gettransactions ' + - JSON.stringify(error)); + logger.error(logSystem, logComponent, 'Check finished - daemon rpc error with batch gettransactions ' + + JSON.stringify(error)); + callback(true); return; } @@ -235,70 +210,51 @@ function SetupForPool(logger, poolOptions, setupFinished){ var round = rounds[i]; - if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){ - - /* Block was dropped from coin daemon even after it happily accepted it earlier. */ - - //If we find another block at the same height then this block was drop-kicked orphaned - var dropKicked = rounds.filter(function(r){ - return r.height === round.height && r.blockHash !== round.blockHash && r.category !== 'dropkicked'; - }).length > 0; - - if (dropKicked){ - logger.warning(logSystem, logComponent, - 'A block was drop-kicked orphaned' - + ' - we found a better block at the same height, blockHash ' - + round.blockHash + " round " + round.height); - round.category = 'dropkicked'; - } - else{ - /* We have no other blocks that match this height so convert to orphan in order for - shares from the round to be rewarded. */ - round.category = 'orphan'; - } + if (tx.error && tx.error.code === -5){ + logger.error(logSystem, logComponent, 'Daemon reports invalid transaction ' + round.txHash + ' ' + + JSON.stringify(tx.error)); + return; } else if (tx.error || !tx.result){ - logger.error(logSystem, logComponent, - 'Error with requesting transaction from block daemon: ' + JSON.stringify(tx)); + logger.error(logSystem, logComponent, 'Odd error with gettransaction ' + round.txHash + ' ' + + JSON.stringify(tx)); + return; } - else{ - round.category = tx.result.details[0].category; - if (round.category === 'generate') - round.amount = tx.result.amount; + else if (round.blockHash !== tx.result.blockhash){ + logger.error(logSystem, logComponent, 'Daemon reports blockhash ' + tx.result.blockhash + + ' for tx ' + round.txHash + ' is not the one we have stored: ' + round.blockHash); + return; } + else if (!(tx.result.details instanceof Array)){ + logger.error(logSystem, logComponent, 'Details array missing from transaction ' + + round.txHash); + return; + } + + var generationTx = tx.result.details.filter(function(tx){ + return tx.address === poolOptions.address; + })[0]; + + if (!generationTx){ + logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + + round.txHash); + return; + } + + round.category = generationTx.category; + if (round.category === 'generate') { + round.reward = generationTx.amount; + } + + }); - var magnitude; - //Filter out all rounds that are immature (not confirmed or orphaned yet) rounds = rounds.filter(function(r){ switch (r.category) { - case 'generate': - /* Here we calculate the smallest unit in this coin's currency; the 'satoshi'. - The rpc.getblocktemplate.amount tells us how much we get in satoshis, while the - rpc.gettransaction.amount tells us how much we get in whole coin units. Therefore, - we simply divide the two to get the magnitude. I don't know math, there is probably - a better term than 'magnitude'. Sue me or do a pull request to fix it. */ - var roundMagnitude = Math.round(r.reward / r.amount); - - if (!magnitude) { - magnitude = roundMagnitude; - - if (roundMagnitude % 10 !== 0) - logger.error(logSystem, logComponent, - 'Satosihis in coin is not divisible by 10 which is very odd'); - } - else if (magnitude != roundMagnitude) { - /* Magnitude for a coin should ALWAYS be the same. For BTC and most coins there are - 100,000,000 satoshis in one coin unit. */ - logger.error(logSystem, logComponent, - 'Magnitude in a round was different than in another round. HUGE PROBLEM.'); - } return true; - - case 'dropkicked': case 'orphan': return true; default: @@ -307,35 +263,30 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - if (rounds.length === 0){ - callback('Check finished - no confirmed or orphaned blocks found'); - } - else{ - callback(null, rounds, magnitude, addressAccount); - } + callback(null, workers, rounds, addressAccount); + }); }, /* Does a batch redis call to get shares contributed to each round. Then calculates the reward amount owned to each miner for each round. */ - function(rounds, magnitude, addressAccount, callback){ + function(workers, rounds, addressAccount, callback){ var shareLookups = rounds.map(function(r){ return ['hgetall', coin + '_shares:round' + r.height] }); - + startRedisTimer(); redisClient.multi(shareLookups).exec(function(error, allWorkerShares){ + endRedisTimer(); + if (error){ - callback('Check finished - redis error with multi get rounds share') + callback('Check finished - redis error with multi get rounds share'); return; } - var orphanMergeCommands = []; - var workerRewards = {}; - rounds.forEach(function(round, i){ var workerShares = allWorkerShares[i]; @@ -352,282 +303,191 @@ function SetupForPool(logger, poolOptions, setupFinished){ miners still get a reward for their work. This seems unfair to those that just started mining during this current round, but over time it balances out and rewards loyal miners. */ - Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', - worker, workerShares[worker]]); - }); + round.workerShares = workerShares; break; case 'generate': /* We found a confirmed block! Now get the reward for it and calculate how much we owe each miner based on the shares they submitted during that block round. */ - var reward = round.reward * (1 - processingConfig.feePercent); + var reward = parseInt(round.reward * magnitude); var totalShares = Object.keys(workerShares).reduce(function(p, c){ return p + parseFloat(workerShares[c]) }, 0); - for (var worker in workerShares){ - var percent = parseFloat(workerShares[worker]) / totalShares; + for (var workerAddress in workerShares){ + var percent = parseFloat(workerShares[workerAddress]) / totalShares; var workerRewardTotal = Math.floor(reward * percent); - if (!(worker in workerRewards)) workerRewards[worker] = 0; - workerRewards[worker] += workerRewardTotal; + var worker = workers[workerAddress] = (workers[workerAddress] || {}); + worker.reward = (worker.reward || 0) + workerRewardTotal; } break; } - }); - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount); + callback(null, workers, rounds, addressAccount); }); }, - /* Does a batch call to redis to get worker existing balances from coin_balances*/ - function(rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount, callback){ - - var workers = Object.keys(workerRewards); - - redisClient.hmget([coin + '_balances'].concat(workers), function(error, results){ - if (error && workers.length !== 0){ - callback('Check finished - redis error with multi get balances ' + JSON.stringify(error)); - return; - } - - - var workerBalances = {}; - - for (var i = 0; i < workers.length; i++){ - workerBalances[workers[i]] = (parseInt(results[i]) || 0); - } - - - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount); - }); - - }, - - /* Calculate if any payments are ready to be sent and trigger them sending Get balance different for each address and pass it along as object of latest balances such as {worker1: balance1, worker2, balance2} when deciding the sent balance, it the difference should be -1*amount they had in db, if not sending the balance, the differnce should be +(the amount they earned this round) */ - function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount, callback){ + function(workers, rounds, addressAccount, callback) { - //number of satoshis in a single coin unit - this can be different for coins so we calculate it :) - - daemon.cmd('getbalance', [addressAccount || ''], function(results){ - - var totalBalance = results[0].response * magnitude; - var toBePaid = 0; - var workerPayments = {}; - - - var balanceUpdateCommands = []; - var workerPayoutsCommand = []; - - /* Here we add up all workers' previous unpaid balances plus their current rewards as we are - about to check if they reach the payout threshold. */ - for (var worker in workerRewards){ - workerPayments[worker] = ((workerPayments[worker] || 0) + workerRewards[worker]); - } - for (var worker in workerBalances){ - workerPayments[worker] = ((workerPayments[worker] || 0) + workerBalances[worker]); - } - - /* Here we check if any of the workers reached their payout threshold, or delete them from the - pending payment ledger (the workerPayments object). */ - if (Object.keys(workerPayments).length > 0){ - var coinPrecision = magnitude.toString().length - 1; - for (var worker in workerPayments){ - if (workerPayments[worker] < processingConfig.minimumPayment * magnitude){ - /* The workers total earnings (balance + current reward) was not enough to warrant - a transaction, so we will store their balance in the database. Next time they - are rewarded it might reach the payout threshold. */ - balanceUpdateCommands.push([ - 'hincrby', - coin + '_balances', - worker, - workerRewards[worker] - ]); - delete workerPayments[worker]; - } - else{ - //If worker had a balance that is about to be paid out, subtract it from the database - if (workerBalances[worker] !== 0){ - balanceUpdateCommands.push([ - 'hincrby', - coin + '_balances', - worker, - -1 * workerBalances[worker] - ]); - } - var rewardInPrecision = (workerRewards[worker] / magnitude).toFixed(coinPrecision); - workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', worker, rewardInPrecision]); - toBePaid += workerPayments[worker]; - } - } - - } - - // txfee included in feeAmountToBeCollected - var leftOver = toBePaid / (1 - processingConfig.feePercent); - var feeAmountToBeCollected = toPrecision(leftOver * processingConfig.feePercent, coinPrecision); - var balanceLeftOver = totalBalance - toBePaid - feeAmountToBeCollected; - var minReserveSatoshis = processingConfig.minimumReserve * magnitude; - if (balanceLeftOver < minReserveSatoshis){ - /* TODO: Need to convert all these variables into whole coin units before displaying because - humans aren't good at reading satoshi units. */ - callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + - (toBePaid/magnitude) + ' and collect ' + (feeAmountToBeCollected/magnitude) + ' as fees' + - ' but only have ' + (totalBalance/magnitude) + '. Left over balance would be ' + (balanceLeftOver/magnitude) + - ', needs to be at least ' + (minReserveSatoshis/magnitude)); - return; - } - - - /* Move pending blocks into either orphan for confirmed sets, and delete their no longer - required round/shares data. */ - var movePendingCommands = []; - var roundsToDelete = []; - rounds.forEach(function(r){ - - var destinationSet = (function(){ - switch(r.category){ - case 'orphan': return '_blocksOrphaned'; - case 'generate': return '_blocksConfirmed'; - case 'dropkicked': return '_blocksDropKicked'; - } - })(); - movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]); - if (r.category === 'generate') - roundsToDelete.push(coin + '_shares:round' + r.height) - }); - - var finalRedisCommands = []; - - if (movePendingCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(movePendingCommands); - - if (orphanMergeCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(orphanMergeCommands); - - if (balanceUpdateCommands.length > 0) - finalRedisCommands = finalRedisCommands.concat(balanceUpdateCommands); - - if (workerPayoutsCommand.length > 0) - finalRedisCommands = finalRedisCommands.concat(workerPayoutsCommand); - - if (roundsToDelete.length > 0) - finalRedisCommands.push(['del'].concat(roundsToDelete)); - - if (toBePaid !== 0) - finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', (toBePaid / magnitude).toFixed(coinPrecision)]); - - finalRedisCommands.push(['del', coin + '_finalRedisCommands']); - - finalRedisCommands.push(['bgsave']); - - callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); - - }); - }, - - function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback) { - /* Save final redis cleanout commands in case something goes wrong during payments */ - redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { - if (error){ - callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); - return; - } - callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); - }); - }, - - function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback){ - - //This does the final all-or-nothing atom transaction if block deamon sent payments - var finalizeRedisTx = function(){ - redisClient.multi(finalRedisCommands).exec(function(error, results){ - if (error){ - callback('Error with final redis commands for cleaning up ' + JSON.stringify(error)); - return; - } - processingPayments = false; - logger.debug(logSystem, logComponent, 'Payments processing performed an interval'); - }); - }; - - if (Object.keys(workerPayments).length === 0){ - finalizeRedisTx(); - } - else{ - - //This is how many decimal places to round a coin down to - var coinPrecision = magnitude.toString().length - 1; + var trySend = function (withholdPercent) { var addressAmounts = {}; - var totalAmountUnits = 0; - for (var address in workerPayments){ - var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision); - var properAddress = getProperAddress(address); - if (!properAddress){ - logger.error(logSystem, logComponent, 'Could not convert pubkey ' + address + ' into address'); - continue; + var totalSent = 0; + for (var w in workers) { + var worker = workers[w]; + worker.balance = worker.balance || 0; + worker.reward = worker.reward || 0; + var toSend = (worker.balance + worker.reward) * (1 - withholdPercent); + if (toSend >= minPaymentSatoshis) { + totalSent += toSend; + var address = worker.address = (worker.address || getProperAddress(w)); + worker.sent = addressAmounts[address] = satoshisToCoins(toSend); + worker.balanceChange = Math.min(worker.balance, toSend) * -1; + } + else { + worker.balanceChange = Math.max(toSend - worker.balance, 0); + worker.sent = 0; } - addressAmounts[properAddress] = coinUnits; - totalAmountUnits += coinUnits; } - logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); + if (Object.keys(addressAmounts).length === 0){ + callback(null, workers, rounds); + return; + } - processingPayments = true; - daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){ - - if (results[0].error){ - callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error)); - return; + daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { + if (result.error && result.error.code === -6) { + var higherPercent = withholdPercent + 0.01; + console.log('asdfasdfsadfasdf'); + logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + + (higherPercent * 100) + '% and retrying'); + trySend(higherPercent); } - - finalizeRedisTx(); - - var totalWorkers = Object.keys(workerPayments).length; - - logger.debug(logSystem, logComponent, 'Payments sent, a total of ' + totalAmountUnits - + ' ' + poolOptions.coin.symbol + ' was sent to ' + totalWorkers + ' miners'); - - daemon.cmd('gettransaction', [results[0].response], function(results){ - if (results[0].error){ - callback('Check finished - error with gettransaction ' + JSON.stringify(results[0].error)); - return; + else if (result.error) { + logger.error(logSystem, logComponent, 'Error trying to send payments wtih RCP sendmany ' + + JSON.stringify(result.error)); + callback(true); + } + else { + logger.debug(logSystem, logComponent, 'Sent out a total of ' + (totalSent / magnitude) + + ' to ' + Object.keys(addressAmounts).length + ' workers'); + if (withholdPercent > 0) { + logger.warning(logSystem, logComponent, 'Had to withhold ' + (withholdPercent * 100) + + '% of reward from miners to cover transaction fees. ' + + 'Fund pool wallet with coins to prevent this from happening'); } - var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision)); - var poolFees = feeAmountUnits - results[0].response.fee; - daemon.cmd('move', [addressAccount || '', processingConfig.feeCollectAccount, poolFees], function(results){ - if (results[0].error){ - callback('Check finished - error with move ' + JSON.stringify(results[0].error)); - return; - } - callback(null, poolFees + ' ' + poolOptions.coin.symbol + ' collected as pool fee'); - }); - }); - }); - } - } - ], function(error, result){ + callback(null, workers, rounds); + } + }, true, true); + }; + trySend(0); + }, + function(workers, rounds, callback){ + + var totalPaid = 0; + + var balanceUpdateCommands = []; + var workerPayoutsCommand = []; + + for (var w in workers) { + var worker = workers[w]; + if (worker.balanceChange !== 0){ + balanceUpdateCommands.push([ + 'hincrby', + coin + '_balances', + w, + worker.balanceChange + ]); + } + if (worker.sent !== 0){ + workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', w, worker.sent]); + totalPaid += worker.sent; + } + } + + + + var movePendingCommands = []; + var roundsToDelete = []; + var orphanMergeCommands = []; + + rounds.forEach(function(r){ + + switch(r.category){ + case 'orphan': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); + var workerShares = r.workerShares; + Object.keys(workerShares).forEach(function(worker){ + orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + worker, workerShares[worker]]); + }); + break; + case 'generate': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); + roundsToDelete.push(coin + '_shares:round' + r.height); + break; + } + + }); + + var finalRedisCommands = []; + + if (movePendingCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(movePendingCommands); + + if (orphanMergeCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(orphanMergeCommands); + + if (balanceUpdateCommands.length > 0) + finalRedisCommands = finalRedisCommands.concat(balanceUpdateCommands); + + if (workerPayoutsCommand.length > 0) + finalRedisCommands = finalRedisCommands.concat(workerPayoutsCommand); + + if (roundsToDelete.length > 0) + finalRedisCommands.push(['del'].concat(roundsToDelete)); + + if (totalPaid !== 0) + finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', totalPaid]); + + if (finalRedisCommands.length === 0){ + callback(); + return; + } + + startRedisTimer(); + redisClient.multi(finalRedisCommands).exec(function(error, results){ + endRedisTimer(); + if (error){ + clearInterval(paymentInterval); + logger.error(logSystem, logComponent, + 'Payments sent but could not update redis. ' + JSON.stringify(error) + + ' Disabling payment processing to prevent possible double-payouts. The redis commands in ' + + coin + '_finalRedisCommands.txt must be ran manually'); + fs.writeFile(coin + '_finalRedisCommands.txt', JSON.stringify(finalRedisCommands), function(err){ + logger.error('Could not write finalRedisCommands.txt, you are fucked.'); + }); + } + callback(); + }); + } + + ], function(){ var paymentProcessTime = Date.now() - startPaymentProcess; + logger.debug(logSystem, logComponent, 'Finished interval - time spent: ' + + paymentProcessTime + 'ms total, ' + timeSpentRedis + 'ms redis, ' + + timeSpentRPC + 'ms daemon RPC'); - if (error) - logger.debug(logSystem, logComponent, '[Took ' + paymentProcessTime + 'ms] ' + error); - - else{ - logger.debug(logSystem, logComponent, '[' + paymentProcessTime + 'ms] ' + result); - // not sure if we need some time to let daemon update the wallet balance - setTimeout(withdrawalProfit, 1000); - } }); }; @@ -639,37 +499,5 @@ function SetupForPool(logger, poolOptions, setupFinished){ else return address; }; - var withdrawalProfit = function(){ - if (!processingConfig.feeWithdrawalThreshold) return; - - logger.debug(logSystem, logComponent, 'Profit withdrawal started'); - daemon.cmd('getbalance', [processingConfig.feeCollectAccount], function(results){ - - // We have to pay some tx fee here too but maybe we shoudn't really care about it too much as long as fee is less - // then minimumReserve value. Because in this case even if feeCollectAccount account will have negative balance - // total wallet balance will be positive and feeCollectAccount account will be refilled during next payment processing. - var withdrawalAmount = results[0].response; - - if (withdrawalAmount < processingConfig.feeWithdrawalThreshold){ - logger.debug(logSystem, logComponent, 'Not enough profit to withdraw yet'); - } - else{ - - var withdrawal = {}; - withdrawal[processingConfig.feeReceiveAddress] = withdrawalAmount; - - daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){ - if (results[0].error){ - logger.debug(logSystem, logComponent, 'Profit withdrawal of ' + withdrawalAmount + ' failed - error with sendmany ' - + JSON.stringify(results[0].error)); - return; - } - logger.debug(logSystem, logComponent, 'Profit sent, a total of ' + withdrawalAmount - + ' ' + poolOptions.coin.symbol + ' was sent to ' + processingConfig.feeReceiveAddress); - }); - } - }); - - }; } \ No newline at end of file diff --git a/libs/poolWorker.js b/libs/poolWorker.js index fc310b7..faeb247 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -108,10 +108,8 @@ module.exports = function(logger){ diff: function(){} }; - var shareProcessing = poolOptions.shareProcessing; - //Functions required for MPOS compatibility - if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ + if (poolOptions.mposMode && poolOptions.mposMode.enabled){ var mposCompat = new MposCompatibility(logger, poolOptions); handlers.auth = function(port, workerName, password, authCallback){ @@ -128,12 +126,12 @@ module.exports = function(logger){ } //Functions required for internal payment processing - else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){ + else{ var shareProcessor = new ShareProcessor(logger, poolOptions); handlers.auth = function(port, workerName, password, authCallback){ - if (shareProcessing.internal.validateWorkerAddress !== true) + if (poolOptions.validateWorkerUsername !== true) authCallback(true); else { port = port.toString(); @@ -238,10 +236,7 @@ module.exports = function(logger){ });*/ redisClient.hgetall("proxyState", function(error, obj) { - if (error || obj == null) { - //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); - } - else { + if (!error && obj) { proxyState = obj; logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); } @@ -258,64 +253,49 @@ module.exports = function(logger){ var algorithm = portalConfig.switching[switchName].algorithm; - if (portalConfig.switching[switchName].enabled === true) { - var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); - proxySwitch[switchName] = { - algorithm: algorithm, - ports: portalConfig.switching[switchName].ports, - currentPool: initalPool, - servers: [] - }; + if (!portalConfig.switching[switchName].enabled) return; - // Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up - // - // Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was - // routed into instead. - // - /*if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { - proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); - proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; - }*/ + var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); + proxySwitch[switchName] = { + algorithm: algorithm, + ports: portalConfig.switching[switchName].ports, + currentPool: initalPool, + servers: [] + }; - - Object.keys(pools).forEach(function (coinName) { - var p = pools[coinName]; - if (poolConfigs[coinName].coin.algorithm === algorithm) { - for (var port in portalConfig.switching[switchName].ports) { - if (portalConfig.switching[switchName].ports[port].varDiff) - p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); - } + Object.keys(pools).forEach(function (coinName) { + var p = pools[coinName]; + if (poolConfigs[coinName].coin.algorithm === algorithm) { + for (var port in portalConfig.switching[switchName].ports) { + if (portalConfig.switching[switchName].ports[port].varDiff) + p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); } + } + }); + + + Object.keys(proxySwitch[switchName].ports).forEach(function(port){ + var f = net.createServer(function(socket) { + var currentPool = proxySwitch[switchName].currentPool; + + logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' + + switchName + ' from ' + + socket.remoteAddress + ' on ' + + port + ' routing to ' + currentPool); + + pools[currentPool].getStratumServer().handleNewClient(socket); + + }).listen(parseInt(port), function() { + logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName + + '" listening for ' + algorithm + + ' on port ' + port + + ' into ' + proxySwitch[switchName].currentPool); }); + proxySwitch[switchName].servers.push(f); + }); - - Object.keys(proxySwitch[switchName].ports).forEach(function(port){ - var f = net.createServer(function(socket) { - var currentPool = proxySwitch[switchName].currentPool; - - logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' - + switchName + ' from ' - + socket.remoteAddress + ' on ' - + port + ' routing to ' + currentPool); - - pools[currentPool].getStratumServer().handleNewClient(socket); - - }).listen(parseInt(port), function() { - logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName - + '" listening for ' + algorithm - + ' on port ' + port - + ' into ' + proxySwitch[switchName].currentPool); - }); - proxySwitch[switchName].servers.push(f); - }); - - - } - else { - //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); - } }); }); } diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 6e0e6c6..9836f2f 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -399,7 +399,7 @@ module.exports = function(logger){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinName = profitStatus[algo][symbol].name; var poolConfig = poolConfigs[coinName]; - var daemonConfig = poolConfig.shareProcessing.internal.daemon; + var daemonConfig = poolConfig.paymentProcessing.daemon; daemonTasks.push(function(callback){ _this.getDaemonInfoForCoin(symbol, daemonConfig, callback) }); diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index a4e88c9..62d6444 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -16,8 +16,7 @@ value: a hash with.. module.exports = function(logger, poolConfig){ - var internalConfig = poolConfig.shareProcessing.internal; - var redisConfig = internalConfig.redis; + var redisConfig = poolConfig.redis; var coin = poolConfig.coin.name; var forkId = process.env.forkId; @@ -60,7 +59,7 @@ module.exports = function(logger, poolConfig){ if (isValidBlock){ redisCommands.push(['rename', coin + '_shares:roundCurrent', coin + '_shares:round' + shareData.height]); - redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height, shareData.reward].join(':')]); + redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); redisCommands.push(['hincrby', coin + '_stats', 'validBlocks', 1]); } else if (shareData.blockHash){ @@ -70,8 +69,6 @@ module.exports = function(logger, poolConfig){ connection.multi(redisCommands).exec(function(err, replies){ if (err) logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); - //else - //logger.debug(logSystem, logComponent, logSubCat, 'Share data and stats recorded'); }); diff --git a/libs/stats.js b/libs/stats.js index 527e19d..340f2d0 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -35,14 +35,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ var poolConfig = poolConfigs[coin]; - if (!poolConfig.shareProcessing || !poolConfig.shareProcessing.internal){ - logger.error(logSystem, coin, 'Cannot do stats without internal share processing setup'); - canDoStats = false; - return; - } - - var internalConfig = poolConfig.shareProcessing.internal; - var redisConfig = internalConfig.redis; + var redisConfig = poolConfig.redis; for (var i = 0; i < redisClients.length; i++){ var client = redisClients[i]; @@ -115,7 +108,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ var redisCommands = []; - var redisComamndTemplates = [ + var redisCommandTemplates = [ ['zremrangebyscore', '_hashrate', '-inf', '(' + windowTime], ['zrangebyscore', '_hashrate', windowTime, '+inf'], ['hgetall', '_stats'], @@ -124,10 +117,10 @@ module.exports = function(logger, portalConfig, poolConfigs){ ['scard', '_blocksOrphaned'] ]; - var commandsPerCoin = redisComamndTemplates.length; + var commandsPerCoin = redisCommandTemplates.length; client.coins.map(function(coin){ - redisComamndTemplates.map(function(t){ + redisCommandTemplates.map(function(t){ var clonedTemplates = t.slice(0); clonedTemplates[1] = coin + clonedTemplates[1]; redisCommands.push(clonedTemplates); diff --git a/libs/website.js b/libs/website.js index 3af59c5..500428b 100644 --- a/libs/website.js +++ b/libs/website.js @@ -151,7 +151,7 @@ module.exports = function(logger){ for (var pName in poolConfigs){ if (pName.toLowerCase() === c) return { - daemon: poolConfigs[pName].shareProcessing.internal.daemon, + daemon: poolConfigs[pName].paymentProcessing.daemon, address: poolConfigs[pName].address } } diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index 7f75c58..a2cdcdf 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -3,6 +3,13 @@ "coin": "litecoin.json", "address": "n4jSe18kZMCdGcZqaYprShXW6EH1wivUK1", + + "rewardRecipients": { + "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, + "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, + "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 + }, + "blockRefreshInterval": 1000, "txRefreshInterval": 20000, "jobRebroadcastTimeout": 55, @@ -12,50 +19,47 @@ "tcpProxyProtocol": false, - "shareProcessing": { - "internal": { - "enabled": true, - "validateWorkerAddress": true, - "paymentInterval": 20, - "minimumPayment": 70, - "minimumReserve": 10, - "feePercent": 0.05, - "feeCollectAccount": "feesCollected", - "feeReceiveAddress": "mppaGeNaSbG1Q7S6V3gL5uJztMhucgL9Vh", - "feeWithdrawalThreshold": 5, - "daemon": { - "host": "127.0.0.1", - "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - "redis": { - "host": "127.0.0.1", - "port": 6379 - } - }, - "mpos": { - "enabled": false, + "validateWorkerUsername": true, + + "paymentProcessing": { + "enabled": true, + "paymentInterval": 20, + "minimumPayment": 70, + "daemon": { "host": "127.0.0.1", - "port": 3306, - "user": "me", - "password": "mypass", - "database": "ltc", - "stratumAuth": "password" + "port": 19332, + "user": "litecoinrpc", + "password": "testnet" } }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + + "mposMode": { + "enabled": false, + "host": "127.0.0.1", + "port": 3306, + "user": "me", + "password": "mypass", + "database": "ltc", + "checkPassword": true, + "autoCreateWorker": false + }, + "banning": { "enabled": true, - "time": 600, + "time": 300, "invalidPercent": 50, - "checkThreshold": 500, + "checkThreshold": 10, "purgeInterval": 300 }, "ports": { "3008": { - "diff": 8 + "diff": 4 }, "3032": { "diff": 32, @@ -78,20 +82,14 @@ "port": 19332, "user": "litecoinrpc", "password": "testnet" - }, - { - "host": "127.0.0.1", - "port": 19344, - "user": "litecoinrpc", - "password": "testnet" } ], "p2p": { - "enabled": false, + "enabled": true, "host": "127.0.0.1", "port": 19333, - "disableTransactions": true, - "magic": "fcc1b7dc" + "disableTransactions": true } -} + +} \ No newline at end of file From c417fdf96b82f1a58052855b3a84be2b65594dc4 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 2 May 2014 17:01:57 -0600 Subject: [PATCH 02/46] Typo fixes in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 85333f5..d6c3d8f 100644 --- a/README.md +++ b/README.md @@ -317,7 +317,7 @@ Description of options: "address": "mi4iBXbBsydtcc5yFmsff2zCFVX4XG7qJc", //Address to where block rewards are given /* Block rewards go to the configured pool wallet address to later be paid out to miners, - except for a percentages that can go to pool operator(s) as pool fees or donations. + except for a percentage that can go to pool operator(s) as pool fees or donations. Addresses or hashed public keys can be used. */ "rewardRecipients": { "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op @@ -391,7 +391,7 @@ Description of options: "redis": { "host": "127.0.0.1", "port": 6379 - } + }, /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want to use the "emitInvalidBlockHashes" option below if you require it. The config options From 6984c4fccc8a213d6d3f48e8adf6151cbc66e8a9 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 3 May 2014 04:09:51 -0600 Subject: [PATCH 03/46] Typo fixes in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6c3d8f..747ae00 100644 --- a/README.md +++ b/README.md @@ -361,7 +361,7 @@ Description of options: /* To receive payments, miners must connect with their address or mining key as their username. This option will only authenticate miners using an address or mining key. */ - "validateWorkerAddress": true, + "validateWorkerUsername": true, "paymentProcessing": { "enabled": true, From 3a5fc1e281ba582ee5acbe84254448e13358662d Mon Sep 17 00:00:00 2001 From: Ondalf Date: Sat, 3 May 2014 13:19:53 +0300 Subject: [PATCH 04/46] Update dogecoin.json Updated peerMagic and peerMagicTestnet for Dogecoin according to latest updates of main nomp repo. --- coins/dogecoin.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/coins/dogecoin.json b/coins/dogecoin.json index 88e28dc..50962d3 100644 --- a/coins/dogecoin.json +++ b/coins/dogecoin.json @@ -1,5 +1,7 @@ { "name": "Dogecoin", "symbol": "DOGE", - "algorithm": "scrypt" -} \ No newline at end of file + "algorithm": "scrypt", + "peerMagic": "c0c0c0c0", + "peerMagicTestnet": "fcc1b7dc" +} From 7cdf87d9607774681591037b40b1663e5c56b762 Mon Sep 17 00:00:00 2001 From: Ondalf Date: Sat, 3 May 2014 13:22:41 +0300 Subject: [PATCH 05/46] Create saffroncoin.json Added saffroncoin into /coins. --- coins/saffroncoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/saffroncoin.json diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json new file mode 100644 index 0000000..af79a75 --- /dev/null +++ b/coins/saffroncoin.json @@ -0,0 +1,7 @@ +{ + "name": "saffroncoin", + "symbol": "SAF", + "algorithm": "scrypt", + "peerMagic": "fbc0b6db", + "peerMagicTestnet": "01f555a4" +} From b07fd03898c0eadf775c42dbe6009ece9c5093c7 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Sat, 3 May 2014 16:04:55 +0300 Subject: [PATCH 06/46] Update saffroncoin.json Typo in SFR coin. --- coins/saffroncoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json index af79a75..67f72df 100644 --- a/coins/saffroncoin.json +++ b/coins/saffroncoin.json @@ -1,6 +1,6 @@ { "name": "saffroncoin", - "symbol": "SAF", + "symbol": "SFR", "algorithm": "scrypt", "peerMagic": "fbc0b6db", "peerMagicTestnet": "01f555a4" From af3115ba7cb24ee31d37852b5c017a491f8ff194 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Sat, 3 May 2014 16:26:50 +0300 Subject: [PATCH 07/46] Update saffroncoin.json Since I seem to lost this every time... https://github.com/saffroncoin/saffroncoin/blob/master/src/chainparams.cpp#L25 --- coins/saffroncoin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coins/saffroncoin.json b/coins/saffroncoin.json index 67f72df..e96e720 100644 --- a/coins/saffroncoin.json +++ b/coins/saffroncoin.json @@ -2,6 +2,6 @@ "name": "saffroncoin", "symbol": "SFR", "algorithm": "scrypt", - "peerMagic": "fbc0b6db", + "peerMagic": "cf0567ea", "peerMagicTestnet": "01f555a4" } From 29d1cc455f06e7151927f219e3c5c0297319aab7 Mon Sep 17 00:00:00 2001 From: Lucas Jones Date: Sat, 3 May 2014 17:29:31 +0100 Subject: [PATCH 08/46] Fix typo in paymentProcessor.js --- libs/paymentProcessor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index d3f2b51..6536a9d 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -372,7 +372,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ trySend(higherPercent); } else if (result.error) { - logger.error(logSystem, logComponent, 'Error trying to send payments wtih RCP sendmany ' + logger.error(logSystem, logComponent, 'Error trying to send payments with RPC sendmany ' + JSON.stringify(result.error)); callback(true); } @@ -500,4 +500,4 @@ function SetupForPool(logger, poolOptions, setupFinished){ }; -} \ No newline at end of file +} From 7b5462dbb0c1860080c45d7e8e36b12740845d48 Mon Sep 17 00:00:00 2001 From: nicoschtein Date: Sat, 3 May 2014 17:07:27 -0300 Subject: [PATCH 09/46] Added peerMagic to GlobalDenomination --- coins/globaldenomination.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coins/globaldenomination.json b/coins/globaldenomination.json index d43c28b..69cb10d 100644 --- a/coins/globaldenomination.json +++ b/coins/globaldenomination.json @@ -1,5 +1,7 @@ { "name": "GlobalDenomination", "symbol": "GDN", - "algorithm": "x11" + "algorithm": "x11", + "peerMagic": "fec3b9de", + "peerMagicTestnet": "fec4bade" } From 4e872b97769cdde22c1e63e92614f44fdd79defa Mon Sep 17 00:00:00 2001 From: SweetJustice Date: Sun, 4 May 2014 02:26:43 +0100 Subject: [PATCH 10/46] Update battlecoin.json Magic bits --- coins/battlecoin.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coins/battlecoin.json b/coins/battlecoin.json index 1e7fd9e..8bc5498 100644 --- a/coins/battlecoin.json +++ b/coins/battlecoin.json @@ -1,5 +1,7 @@ { "name": "Battlecoin", "symbol": "BCX", - "algorithm": "sha256" + "algorithm": "sha256", + "peerMagic": "03e803e4", + "peerMagicTestnet": "cdf2c0ef" } From cc3fe3ba8eda51f128cd80a681d643919b7394a2 Mon Sep 17 00:00:00 2001 From: zacons Date: Sun, 4 May 2014 09:44:53 +0200 Subject: [PATCH 11/46] Update cliListener.js --- libs/cliListener.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/cliListener.js b/libs/cliListener.js index efb18cf..aa4a0e9 100644 --- a/libs/cliListener.js +++ b/libs/cliListener.js @@ -26,6 +26,9 @@ var listener = module.exports = function listener(port){ }); c.on('end', function () { + }); + c.on('error', function () { + }); } catch(e){ From 81c3a66bde743e2ca9bfdc0b9dcb08e084641143 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 02:21:15 -0600 Subject: [PATCH 12/46] Fixed "missing output details to pool address" error messages for some types of coins --- libs/paymentProcessor.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 6536a9d..12513fc 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -235,6 +235,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ return tx.address === poolOptions.address; })[0]; + + if (!generationTx && tx.result.details.length === 1){ + generationTx = tx.results.details[0]; + } + if (!generationTx){ logger.error(logSystem, logComponent, 'Missing output details to pool address for transaction ' + round.txHash); @@ -243,7 +248,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ round.category = generationTx.category; if (round.category === 'generate') { - round.reward = generationTx.amount; + round.reward = generationTx.amount || generationTx.value; } From b1e05dda1d1edaa8c3a3c09ea0b2e8c31a9b6614 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 4 May 2014 19:55:12 +1000 Subject: [PATCH 13/46] Added Globalcoin --- coins/globalcoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/globalcoin.json diff --git a/coins/globalcoin.json b/coins/globalcoin.json new file mode 100644 index 0000000..d2d3427 --- /dev/null +++ b/coins/globalcoin.json @@ -0,0 +1,7 @@ +{ + "name": "Globalcoin", + "symbol": "GLC", + "algorithm": "scrypt", + "peerMagic": "fcd9b7dd", + "peerMagicTestnet": "fbc0b8db" +} From fd7bf439e5d3396936f952b343bae8271197953e Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 13:51:01 -0600 Subject: [PATCH 14/46] Fixed typo in my last commit that fixed payment processing for some coins. --- libs/paymentProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 12513fc..bcccb74 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -237,7 +237,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ if (!generationTx && tx.result.details.length === 1){ - generationTx = tx.results.details[0]; + generationTx = tx.result.details[0]; } if (!generationTx){ From 964335340b604427e5b1b6d7e39831230c319f09 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 15:29:59 -0600 Subject: [PATCH 15/46] More informative error when failing to parse satoshis in coin for payment processing --- libs/paymentProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index bcccb74..1e48e49 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -90,7 +90,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback(); } catch(e){ - logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing'); + logger.error(logSystem, logComponent, 'Error detecting number of satoshis in a coin, cannot do payment processing. Tried parsing: ' + result.data); callback(true); } From cbebb94a57ce7a571ff571458c9b0932b130b84a Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 4 May 2014 15:51:48 -0600 Subject: [PATCH 16/46] Slight readme update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 747ae00..63afaf5 100644 --- a/README.md +++ b/README.md @@ -317,13 +317,15 @@ Description of options: "address": "mi4iBXbBsydtcc5yFmsff2zCFVX4XG7qJc", //Address to where block rewards are given /* Block rewards go to the configured pool wallet address to later be paid out to miners, - except for a percentage that can go to pool operator(s) as pool fees or donations. - Addresses or hashed public keys can be used. */ + except for a percentage that can go to, for examples, pool operator(s) as pool fees or + or to donations address. Addresses or hashed public keys can be used. Here is an example + of rewards going to the main pool op, a pool co-owner, and NOMP donation. */ "rewardRecipients": { "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, //0.5% goes to a pool co-owner //0.1% donation to NOMP to help support development + //This NOMP pubkey and accept any type of coin "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From 8ccf9474823d583aa0413f2173422e7246c8ed3d Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Sun, 4 May 2014 18:53:06 -0600 Subject: [PATCH 17/46] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 63afaf5..80b57fa 100644 --- a/README.md +++ b/README.md @@ -324,8 +324,8 @@ Description of options: "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, //0.5% goes to a pool co-owner - //0.1% donation to NOMP to help support development - //This NOMP pubkey and accept any type of coin + //0.1% donation to NOMP. This pubkey can accept any type of coin, please leave this in + your config to help support NOMP development. "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From d562bb2a7e1b68ee879d60e56f47228d5ef22def Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Sun, 4 May 2014 18:56:13 -0600 Subject: [PATCH 18/46] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 80b57fa..34ccac4 100644 --- a/README.md +++ b/README.md @@ -324,8 +324,8 @@ Description of options: "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, //1.5% goes to pool op "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, //0.5% goes to a pool co-owner - //0.1% donation to NOMP. This pubkey can accept any type of coin, please leave this in - your config to help support NOMP development. + /* 0.1% donation to NOMP. This pubkey can accept any type of coin, please leave this in + your config to help support NOMP development. */ "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, From 86bd8d91fae7706ac00b290380a3c1598cfdc4ca Mon Sep 17 00:00:00 2001 From: mcpackin Date: Mon, 5 May 2014 08:55:36 +0000 Subject: [PATCH 19/46] Added/updated coins. --- coins/fastcoin.json | 3 ++- coins/starcoin.json | 3 ++- coins/ultimatecoin.json | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 coins/ultimatecoin.json diff --git a/coins/fastcoin.json b/coins/fastcoin.json index 077f767..c1af320 100644 --- a/coins/fastcoin.json +++ b/coins/fastcoin.json @@ -1,5 +1,6 @@ { "name": "Fastcoin", "symbol": "FST", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "fbc0b6db" } \ No newline at end of file diff --git a/coins/starcoin.json b/coins/starcoin.json index ca65a81..65f5f42 100644 --- a/coins/starcoin.json +++ b/coins/starcoin.json @@ -1,5 +1,6 @@ { "name": "Starcoin", "symbol": "STR", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "e4e8effd" } diff --git a/coins/ultimatecoin.json b/coins/ultimatecoin.json new file mode 100644 index 0000000..1df3370 --- /dev/null +++ b/coins/ultimatecoin.json @@ -0,0 +1,6 @@ +{ + "name": "Ultimatecoin", + "symbol": "ULT", + "algorithm": "scrypt", + "peerMagic": "f9f7c0e8" +} From 5d8138c3a2d45670f9ca1e65f8d8466220c95e0b Mon Sep 17 00:00:00 2001 From: mcpackin Date: Mon, 5 May 2014 09:04:29 +0000 Subject: [PATCH 20/46] Added grandcoin.json --- coins/grandcoin.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 coins/grandcoin.json diff --git a/coins/grandcoin.json b/coins/grandcoin.json new file mode 100644 index 0000000..748d3cf --- /dev/null +++ b/coins/grandcoin.json @@ -0,0 +1,7 @@ +{ + "name": "Grandcoin", + "symbol": "GDC", + "algorithm": "scrypt", + "peerMagic": "fdc1a5db", + "txMessages": true +} From c3a15ad200d6e5095931af06af48e43a31676e6b Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 6 May 2014 20:30:31 -0600 Subject: [PATCH 21/46] Updated to support slightly different logging implemented while doing new Darkcoin masternode features --- libs/paymentProcessor.js | 4 +++- libs/profitSwitch.js | 7 +++---- libs/website.js | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 1e48e49..ecffef6 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -49,7 +49,9 @@ function SetupForPool(logger, poolOptions, setupFinished){ var logSystem = 'Payments'; var logComponent = coin; - var daemon = new Stratum.daemon.interface([processingConfig.daemon]); + var daemon = new Stratum.daemon.interface([processingConfig.daemon], function(severity, message){ + logger[severity](logSystem, logComponent, message); + }); var redisClient = redis.createClient(poolOptions.redis.port, poolOptions.redis.host); var magnitude; diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 9836f2f..5650ffa 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -419,11 +419,10 @@ module.exports = function(logger){ }); }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ - var daemon = new Stratum.daemon.interface([cfg]); - daemon.on('error', function(error){ - logger.error(logSystem, symbol, JSON.stringify(error)); + var daemon = new Stratum.daemon.interface([cfg], function(severity, message){ + logger[severity](logSystem, symbol, message); callback(null); // fail gracefully for each coin - }).init(); + }); daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) { if (result[0].error != null) { diff --git a/libs/website.js b/libs/website.js index 500428b..6db3ed4 100644 --- a/libs/website.js +++ b/libs/website.js @@ -156,10 +156,12 @@ module.exports = function(logger){ } } })(); - var daemon = new Stratum.daemon.interface([coinInfo.daemon]); + var daemon = new Stratum.daemon.interface([coinInfo.daemon], function(severity, message){ + logger[severity](logSystem, c, message); + }); daemon.cmd('dumpprivkey', [coinInfo.address], function(result){ if (result[0].error){ - logger.error(logSystem, 'daemon', 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); + logger.error(logSystem, c, 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); cback(); return; } From baadebd97da91ca938a9b85b5ef58ce06298465c Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 01:11:27 -0600 Subject: [PATCH 22/46] Detect kicked blocks and deal with them appropriately --- libs/paymentProcessor.js | 67 ++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index ecffef6..b90f646 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -213,8 +213,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ var round = rounds[i]; if (tx.error && tx.error.code === -5){ - logger.error(logSystem, logComponent, 'Daemon reports invalid transaction ' + round.txHash + ' ' - + JSON.stringify(tx.error)); + logger.warning(logSystem, logComponent, 'Daemon reports invalid transaction: ' + round.txHash); + round.category = 'kicked'; + 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){ @@ -222,16 +227,6 @@ function SetupForPool(logger, poolOptions, setupFinished){ + JSON.stringify(tx)); return; } - else if (round.blockHash !== tx.result.blockhash){ - logger.error(logSystem, logComponent, 'Daemon reports blockhash ' + tx.result.blockhash - + ' for tx ' + round.txHash + ' is not the one we have stored: ' + round.blockHash); - return; - } - else if (!(tx.result.details instanceof Array)){ - logger.error(logSystem, logComponent, 'Details array missing from transaction ' - + round.txHash); - return; - } var generationTx = tx.result.details.filter(function(tx){ return tx.address === poolOptions.address; @@ -253,16 +248,29 @@ function SetupForPool(logger, poolOptions, setupFinished){ round.reward = generationTx.amount || generationTx.value; } - }); + var canDeleteShares = function(r){ + for (var i = 0; i < rounds.length; i++){ + var compareR = rounds[i]; + if ((compareR.height === r.height) + && (compareR.category !== 'kicked') + && (compareR.category !== 'orphan') + && (compareR.serialized !== r.serialized)){ + return false; + } + } + return true; + }; + //Filter out all rounds that are immature (not confirmed or orphaned yet) rounds = rounds.filter(function(r){ switch (r.category) { - case 'generate': - return true; case 'orphan': + case 'kicked': + r.canDeleteShares = canDeleteShares(r); + case 'generate': return true; default: return false; @@ -305,11 +313,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ } switch (round.category){ + case 'kicked': case 'orphan': - /* Each block that gets orphaned, all the shares go into the current round so that - miners still get a reward for their work. This seems unfair to those that just - started mining during this current round, but over time it balances out and rewards - loyal miners. */ round.workerShares = workerShares; break; @@ -373,7 +378,6 @@ function SetupForPool(logger, poolOptions, setupFinished){ daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { if (result.error && result.error.code === -6) { var higherPercent = withholdPercent + 0.01; - console.log('asdfasdfsadfasdf'); logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + (higherPercent * 100) + '% and retrying'); trySend(higherPercent); @@ -427,21 +431,30 @@ function SetupForPool(logger, poolOptions, setupFinished){ var roundsToDelete = []; var orphanMergeCommands = []; + var moveSharesToCurrent = function(r){ + var workerShares = r.workerShares; + Object.keys(workerShares).forEach(function(worker){ + orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + worker, workerShares[worker]]); + }); + }; + rounds.forEach(function(r){ switch(r.category){ + case 'kicked': + movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksKicked', r.serialized]); case 'orphan': movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); - var workerShares = r.workerShares; - Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', - worker, workerShares[worker]]); - }); - break; + if (r.canDeleteShares){ + moveSharesToCurrent(r); + roundsToDelete.push(coin + '_shares:round' + r.height); + } + return; case 'generate': movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); roundsToDelete.push(coin + '_shares:round' + r.height); - break; + return; } }); From 036f246ab7d51debbade4a6b49e6d2eae17db46e Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 01:23:50 -0600 Subject: [PATCH 23/46] Allow stratum authentication with mining key on non-switching ports --- libs/poolWorker.js | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/libs/poolWorker.js b/libs/poolWorker.js index faeb247..dc48282 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -134,30 +134,24 @@ module.exports = function(logger){ if (poolOptions.validateWorkerUsername !== true) authCallback(true); else { - port = port.toString(); - if (portalConfig.switching) { - for (var switchName in portalConfig.switching) { - if (portalConfig.switching[switchName].enabled && Object.keys(portalConfig.switching[switchName].ports).indexOf(port) !== -1) { - if (workerName.length === 40) { - try { - new Buffer(workerName, 'hex'); - authCallback(true); - } - catch (e) { - authCallback(false); - } - } - else - authCallback(false); - return; - } + if (workerName.length === 40) { + try { + new Buffer(workerName, 'hex'); + authCallback(true); + } + catch (e) { + authCallback(false); } } + else { + pool.daemon.cmd('validateaddress', [workerName], function (results) { + var isValid = results.filter(function (r) { + return r.response.isvalid + }).length > 0; + authCallback(isValid); + }); + } - pool.daemon.cmd('validateaddress', [workerName], function(results){ - var isValid = results.filter(function(r){return r.response.isvalid}).length > 0; - authCallback(isValid); - }); } }; From 7ae0107eea04b81ff09c5c730290fa6baa2a99f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Wed, 7 May 2014 12:45:25 +0300 Subject: [PATCH 24/46] added totalPaid to /api/stats/ --- libs/stats.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/stats.js b/libs/stats.js index 340f2d0..75cfc91 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -144,7 +144,8 @@ module.exports = function(logger, portalConfig, poolConfigs){ poolStats: { validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0, validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0, - invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0 + invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0, + totalPaid: replies[i + 2] ? (replies[i + 2].totalPaid || 0) : 0 }, blocks: { pending: replies[i + 3], From 3fb907f6821d99662b583aa9097f9473cc944492 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 11:58:56 -0600 Subject: [PATCH 25/46] Changing _balances value to be stored in coin units rather than satoshi units --- libs/paymentProcessor.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index b90f646..2fd2fad 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -121,6 +121,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ return parseFloat((satoshis / magnitude).toFixed(coinPrecision)); }; + var coinsToSatoshies = function(coins){ + return coins * magnitude; + }; + /* Deal with numbers in smallest possible units (satoshis) as much as possible. This greatly helps with accuracy when rounding and whatnot. When we are storing numbers for only humans to see, store in whole coin units. */ @@ -163,7 +167,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var workers = {}; for (var w in results[0]){ - workers[w] = {balance: parseInt(results[0][w])}; + workers[w] = {balance: coinsToSatoshies(parseInt(results[0][w]))}; } var rounds = results[1].map(function(r){ @@ -376,9 +380,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ } daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function (result) { + //Check if payments failed because wallet doesn't have enough coins to pay for tx fees if (result.error && result.error.code === -6) { var higherPercent = withholdPercent + 0.01; - logger.warning(logSystem, logComponent, 'Not enough funds to send out payments, decreasing rewards by ' + logger.warning(logSystem, logComponent, 'Not enough funds to cover the tx fees for sending out payments, decreasing rewards by ' + (higherPercent * 100) + '% and retrying'); trySend(higherPercent); } @@ -413,10 +418,10 @@ function SetupForPool(logger, poolOptions, setupFinished){ var worker = workers[w]; if (worker.balanceChange !== 0){ balanceUpdateCommands.push([ - 'hincrby', + 'hincrbyfloat', coin + '_balances', w, - worker.balanceChange + satoshisToCoins(worker.balanceChange) ]); } if (worker.sent !== 0){ From 57462c3de103ec5ebeb98aef1c460d1049008819 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 11:59:37 -0600 Subject: [PATCH 26/46] Better error handling for when website cannot start listening on configured port --- libs/website.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/libs/website.js b/libs/website.js index 6db3ed4..1b246be 100644 --- a/libs/website.js +++ b/libs/website.js @@ -274,9 +274,15 @@ module.exports = function(logger){ res.send(500, 'Something broke!'); }); - app.listen(portalConfig.website.port, function(){ - logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); - }); + try { + app.listen(portalConfig.website.port, function () { + logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); + }); + } + catch(e){ + logger.error(logSystem, 'Server', 'Could not start website on port ' + portalConfig.website.port + + ' - its either in use or you do not have permission'); + } }; From 537fddfc433c650b38e34ad5ead6285a8694a161 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:00:06 -0600 Subject: [PATCH 27/46] Removed "txRefreshInterval" option from readme as its no longer used --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 34ccac4..e668861 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,6 @@ Description of options: "blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds - /* How many milliseconds should have passed before new block transactions will trigger a new - job broadcast. */ - "txRefreshInterval": 20000, /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast From 1f7b7c36a8b9d1d6cc38dead1030bf5f8ddb7d9b Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:00:40 -0600 Subject: [PATCH 28/46] Remove txRefreshInterval from example config as its not longer used --- pool_configs/litecoin_example.json | 1 - 1 file changed, 1 deletion(-) diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index a2cdcdf..e9512b8 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -11,7 +11,6 @@ }, "blockRefreshInterval": 1000, - "txRefreshInterval": 20000, "jobRebroadcastTimeout": 55, "connectionTimeout": 600, "emitInvalidBlockHashes": false, From d8444e6a6caea6be8525028e542cdbdfea28ab4c Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:22:32 -0600 Subject: [PATCH 29/46] Removed "shareVariancePercent" option as its no longer useful --- README.md | 6 ------ libs/shareProcessor.js | 4 +++- pool_configs/litecoin_example.json | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e668861..da4b2ed 100644 --- a/README.md +++ b/README.md @@ -347,12 +347,6 @@ Description of options: /* Sometimes you want the block hashes even for shares that aren't block candidates. */ "emitInvalidBlockHashes": false, - /* We use proper maximum algorithm difficulties found in the coin daemon source code. Most - miners/pools that deal with scrypt use a guesstimated one that is about 5.86% off from the - actual one. So here we can set a tolerable threshold for if a share is slightly too low - due to mining apps using incorrect max diffs and this pool using correct max diffs. */ - "shareVariancePercent": 2, - /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy protocol enabled, such as HAProxy with 'send-proxy' param: http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 62d6444..aeeda49 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -19,6 +19,7 @@ module.exports = function(logger, poolConfig){ var redisConfig = poolConfig.redis; var coin = poolConfig.coin.name; + var forkId = process.env.forkId; var logSystem = 'Pool'; var logComponent = coin; @@ -51,7 +52,8 @@ module.exports = function(logger, poolConfig){ 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(); - redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, [shareData.difficulty, shareData.worker, dateNow].join(':')]); + var hashrateData = [shareData.difficulty, shareData.worker, dateNow]; + redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); } else{ redisCommands.push(['hincrby', coin + '_stats', 'invalidShares', 1]); diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index e9512b8..ed5f414 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -14,7 +14,6 @@ "jobRebroadcastTimeout": 55, "connectionTimeout": 600, "emitInvalidBlockHashes": false, - "shareVariancePercent": 15, "tcpProxyProtocol": false, From 81b7f5053693dcc8b7e4a009c0f45ef24f754024 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 7 May 2014 12:38:39 -0600 Subject: [PATCH 30/46] Coin names are now converted to lowercase. --- init.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.js b/init.js index aacb92e..dc01c7a 100644 --- a/init.js +++ b/init.js @@ -56,7 +56,6 @@ catch(e){ } - if (cluster.isWorker){ switch(process.env.workerType){ @@ -132,6 +131,7 @@ var buildPoolConfigs = function(){ var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); poolOptions.coin = coinProfile; + poolOptions.coin.name = poolOptions.coin.name.toLowerCase(); if (poolOptions.coin.name in configs){ From c5ba95f1d90796d360dcfe9385fd523057c1816f Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 7 May 2014 13:27:24 -0600 Subject: [PATCH 31/46] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index da4b2ed..5602aac 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ This portal is an extremely efficient, highly scalable, all-in-one, easy to setu entirely in Node.js. It contains a stratum poolserver; reward/payment/share processor; and a (*not yet completed*) responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. +#### Production Usage Notice +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. + + #### Table of Contents * [Features](#features) * [Attack Mitigation](#attack-mitigation) From d7db8a3af6ff631365fb3c7df9e4d1f4267a66e7 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Thu, 8 May 2014 14:54:44 +0200 Subject: [PATCH 32/46] Allow disable log colors --- config_example.json | 1 + init.js | 3 ++- libs/logUtil.js | 27 ++++++++++++++++++++------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/config_example.json b/config_example.json index bc09c9b..7f49ad7 100644 --- a/config_example.json +++ b/config_example.json @@ -1,5 +1,6 @@ { "logLevel": "debug", + "logColors": true, "cliPort": 17117, diff --git a/init.js b/init.js index dc01c7a..4ed6d90 100644 --- a/init.js +++ b/init.js @@ -26,7 +26,8 @@ var poolConfigs; var logger = new PoolLogger({ - logLevel: portalConfig.logLevel + logLevel: portalConfig.logLevel, + logColors: portalConfig.logColors }); diff --git a/libs/logUtil.js b/libs/logUtil.js index 7d25275..7b56b98 100644 --- a/libs/logUtil.js +++ b/libs/logUtil.js @@ -30,6 +30,7 @@ var PoolLogger = function (configuration) { var logLevelInt = severityValues[configuration.logLevel]; + var logColors = configuration.logColors; @@ -45,16 +46,28 @@ var PoolLogger = function (configuration) { } var entryDesc = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss') + ' [' + system + ']\t'; - entryDesc = severityToColor(severity, entryDesc); + if (logColors) { + entryDesc = severityToColor(severity, entryDesc); - var logString = - entryDesc + - ('[' + component + '] ').italic; + var logString = + entryDesc + + ('[' + component + '] ').italic; - if (subcat) - logString += ('(' + subcat + ') ').bold.grey + if (subcat) + logString += ('(' + subcat + ') ').bold.grey; - logString += text.grey; + logString += text.grey; + } + else { + var logString = + entryDesc + + '[' + component + '] '; + + if (subcat) + logString += '(' + subcat + ') '; + + logString += text; + } console.log(logString); From 0de705abb1626ebd9cebc795a06f5177d56f2549 Mon Sep 17 00:00:00 2001 From: Ari Velakoski Date: Thu, 8 May 2014 22:38:26 +0300 Subject: [PATCH 33/46] Update tbs.html Small typo fixed. Firefox kindly pointed this out. --- website/pages/tbs.html | 1 - 1 file changed, 1 deletion(-) diff --git a/website/pages/tbs.html b/website/pages/tbs.html index 9ca8352..09a5d27 100644 --- a/website/pages/tbs.html +++ b/website/pages/tbs.html @@ -46,7 +46,6 @@ Orphaned Hashrate - {{ for(var pool in it.stats.pools) { }} From bc8a40a1c9f05acc3d12d6f262836c5f4968807b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 00:21:55 +0300 Subject: [PATCH 34/46] added peer magic values for lottocoin and vertcoin --- coins/lottocoin.json | 4 +++- coins/vertcoin.json | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/coins/lottocoin.json b/coins/lottocoin.json index 188238a..4d8a28b 100644 --- a/coins/lottocoin.json +++ b/coins/lottocoin.json @@ -1,5 +1,7 @@ { "name": "Lottocoin", "symbol": "LOT", - "algorithm": "scrypt" + "algorithm": "scrypt", + "peerMagic": "a5fdb6c1", + "peerMagicTestnet": "fdc3b6f1" } \ No newline at end of file diff --git a/coins/vertcoin.json b/coins/vertcoin.json index 5e74691..35cfdd4 100644 --- a/coins/vertcoin.json +++ b/coins/vertcoin.json @@ -1,5 +1,7 @@ { "name": "Vertcoin", "symbol": "VTC", - "algorithm": "scrypt-n" + "algorithm": "scrypt-n", + "peerMagic": "fabfb5da", + "peerMagicTestnet": "76657274" } \ No newline at end of file From 647060e1ed909b4d9db1c67ad60772d858a6c801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 00:23:57 +0300 Subject: [PATCH 35/46] fixed tabs --- coins/lottocoin.json | 4 ++-- coins/vertcoin.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/coins/lottocoin.json b/coins/lottocoin.json index 4d8a28b..3b05499 100644 --- a/coins/lottocoin.json +++ b/coins/lottocoin.json @@ -2,6 +2,6 @@ "name": "Lottocoin", "symbol": "LOT", "algorithm": "scrypt", - "peerMagic": "a5fdb6c1", - "peerMagicTestnet": "fdc3b6f1" + "peerMagic": "a5fdb6c1", + "peerMagicTestnet": "fdc3b6f1" } \ No newline at end of file diff --git a/coins/vertcoin.json b/coins/vertcoin.json index 35cfdd4..fc001d4 100644 --- a/coins/vertcoin.json +++ b/coins/vertcoin.json @@ -2,6 +2,6 @@ "name": "Vertcoin", "symbol": "VTC", "algorithm": "scrypt-n", - "peerMagic": "fabfb5da", - "peerMagicTestnet": "76657274" + "peerMagic": "fabfb5da", + "peerMagicTestnet": "76657274" } \ No newline at end of file From ffa2d26b5f3c654750744aa049ee467ef27003ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Uslu?= Date: Fri, 9 May 2014 12:58:36 +0300 Subject: [PATCH 36/46] added einsteinium, feathercoin, groestlcoin, myriadcoin and fixed hirocoin's symbol --- coins/einsteinium.json | 5 +++++ coins/feathercoin.json | 5 +++++ coins/groestlcoin.json | 5 +++++ coins/hirocoin.json | 2 +- coins/myriadcoin.json | 5 +++++ 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 coins/einsteinium.json create mode 100644 coins/feathercoin.json create mode 100644 coins/groestlcoin.json create mode 100644 coins/myriadcoin.json diff --git a/coins/einsteinium.json b/coins/einsteinium.json new file mode 100644 index 0000000..d2575ef --- /dev/null +++ b/coins/einsteinium.json @@ -0,0 +1,5 @@ +{ + "name": "Einsteinium", + "symbol": "EMC2", + "algorithm": "scrypt" +} diff --git a/coins/feathercoin.json b/coins/feathercoin.json new file mode 100644 index 0000000..26959d8 --- /dev/null +++ b/coins/feathercoin.json @@ -0,0 +1,5 @@ +{ + "name": "Feathercoin", + "symbol": "FTC", + "algorithm": "scrypt" +} diff --git a/coins/groestlcoin.json b/coins/groestlcoin.json new file mode 100644 index 0000000..50feee4 --- /dev/null +++ b/coins/groestlcoin.json @@ -0,0 +1,5 @@ +{ + "name": "GroestlCoin", + "symbol": "GRS", + "algorithm": "groestl" +} diff --git a/coins/hirocoin.json b/coins/hirocoin.json index cd7da82..3adec2c 100644 --- a/coins/hirocoin.json +++ b/coins/hirocoin.json @@ -1,6 +1,6 @@ { "name": "Hirocoin", - "symbol": "hic", + "symbol": "HIRO", "algorithm": "x11", "mposDiffMultiplier": 256 } diff --git a/coins/myriadcoin.json b/coins/myriadcoin.json new file mode 100644 index 0000000..bd0f0ee --- /dev/null +++ b/coins/myriadcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Myriadcoin", + "symbol": "MYR", + "algorithm": "scrypt" +} \ No newline at end of file From da0264f8de8bab1bdad58880c1ed663a6d2acf05 Mon Sep 17 00:00:00 2001 From: Elbandi Date: Fri, 9 May 2014 13:41:16 +0200 Subject: [PATCH 37/46] Bind website listener to host --- config_example.json | 1 + libs/website.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config_example.json b/config_example.json index 7f49ad7..c473771 100644 --- a/config_example.json +++ b/config_example.json @@ -11,6 +11,7 @@ "website": { "enabled": true, + "host": "0.0.0.0", "port": 80, "stratumHost": "cryppit.com", "stats": { diff --git a/libs/website.js b/libs/website.js index 1b246be..4156d1f 100644 --- a/libs/website.js +++ b/libs/website.js @@ -275,12 +275,12 @@ module.exports = function(logger){ }); try { - app.listen(portalConfig.website.port, function () { - logger.debug(logSystem, 'Server', 'Website started on port ' + portalConfig.website.port); + app.listen(portalConfig.website.port, portalConfig.website.host, function () { + logger.debug(logSystem, 'Server', 'Website started on ' + portalConfig.website.host + ':' + portalConfig.website.port); }); } catch(e){ - logger.error(logSystem, 'Server', 'Could not start website on port ' + portalConfig.website.port + logger.error(logSystem, 'Server', 'Could not start website on ' + portalConfig.website.host + ':' + portalConfig.website.port + ' - its either in use or you do not have permission'); } From f9c739f123da02532f1e6589f6ffe1a84cdd8061 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Fri, 9 May 2014 09:47:59 -0600 Subject: [PATCH 38/46] Updated with new config options --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5602aac..19fa907 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ entirely in Node.js. It contains a stratum poolserver; reward/payment/share proc responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. #### Production Usage Notice -This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuration files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. #### Table of Contents @@ -171,6 +171,10 @@ Explanation for each field: /* Specifies the level of log output verbosity. Anything more severy than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" + + /* By default NOMP logs to console and gives pretty colors. If you direct that output to a + log file then disable this feature to avoid nasty characters in your log file. */ + "logColors": true, /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, @@ -189,6 +193,9 @@ Explanation for each field: /* This is the front-end. Its not finished. When it is finished, this comment will say so. */ "website": { "enabled": true, + /* If you are using a reverse-proxy like nginx to display the website then set this to + 127.0.0.1 to not expose the port. */ + "host": "0.0.0.0", "port": 80, /* Used for displaying stratum connection data on the Getting Started page. */ "stratumHost": "cryppit.com", From f91ff797183564d60940d00a0b27f8d40e2ce405 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 11:51:08 -0600 Subject: [PATCH 39/46] Added inheritable pool options in global config --- README.md | 155 ++++++++++++++--------------- config_example.json | 27 +++-- init.js | 36 +++---- libs/redisblocknotifyListener.js | 36 ------- package.json | 3 +- pool_configs/litecoin_example.json | 55 +++------- 6 files changed, 127 insertions(+), 185 deletions(-) delete mode 100644 libs/redisblocknotifyListener.js diff --git a/README.md b/README.md index 5602aac..fdbd0e1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ entirely in Node.js. It contains a stratum poolserver; reward/payment/share proc responsive user-friendly front-end website featuring mining instructions, in-depth live statistics, and an admin center. #### Production Usage Notice -This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuratoin files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. +This is beta software. All of the following are things that can change and break an existing NOMP setup: functionality of any feature, structure of configuration files and structure of redis data. If you use this software in production then *DO NOT* pull new code straight into production usage because it can and often will break your setup and require you to tweak things like config files or redis data. #### Table of Contents @@ -171,6 +171,10 @@ Explanation for each field: /* Specifies the level of log output verbosity. Anything more severy than the level specified will also be logged. */ "logLevel": "debug", //or "warning", "error" + + /* By default NOMP logs to console and gives pretty colors. If you direct that output to a + log file then disable this feature to avoid nasty characters in your log file. */ + "logColors": true, /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, @@ -185,10 +189,54 @@ Explanation for each field: "enabled": true, "forks": "auto" }, + + /* Pool config file will inherit these default values if they are not set. */ + "defaultPoolConfigs": { + + /* Poll RPC daemons for new blocks every this many milliseconds. */ + "blockRefreshInterval": 1000, + + /* If no new blocks are available for this many seconds update and rebroadcast job. */ + "jobRebroadcastTimeout": 55, + + /* Disconnect workers that haven't submitted shares for this many seconds. */ + "connectionTimeout": 600, + + /* (For MPOS mode) Store the block hashes for shares that aren't block candidates. */ + "emitInvalidBlockHashes": false, + + /* This option will only authenticate miners using an address or mining key. */ + "validateWorkerUsername": true, + + /* Enable for client IP addresses to be detected when using a load balancer with TCP + proxy protocol enabled, such as HAProxy with 'send-proxy' param: + http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ + "tcpProxyProtocol": false, + + /* If under low-diff share attack we can ban their IP to reduce system/network load. If + running behind HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up + banning your own IP address (and therefore all workers). */ + "banning": { + "enabled": true, + "time": 600, //How many seconds to ban worker for + "invalidPercent": 50, //What percent of invalid shares triggers ban + "checkThreshold": 500, //Perform check when this many shares have been submitted + "purgeInterval": 300 //Every this many seconds clear out the list of old bans + }, + + /* Used for storing share and block submission data and payment processing. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + }, /* This is the front-end. Its not finished. When it is finished, this comment will say so. */ "website": { "enabled": true, + /* If you are using a reverse-proxy like nginx to display the website then set this to + 127.0.0.1 to not expose the port. */ + "host": "0.0.0.0", "port": 80, /* Used for displaying stratum connection data on the Getting Started page. */ "stratumHost": "cryppit.com", @@ -333,33 +381,6 @@ Description of options: "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, - "blockRefreshInterval": 1000, //How often to poll RPC daemons for new blocks, in milliseconds - - - /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs - for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast - in this many seconds unless we find a new job. Set to zero or remove to disable this. */ - "jobRebroadcastTimeout": 55, - - //instanceId: 37, //Recommend not using this because a crypto-random one will be generated - - /* Some attackers will create thousands of workers that use up all available socket connections, - usually the workers are zombies and don't submit shares after connecting. This feature - detects those and disconnects them. */ - "connectionTimeout": 600, //Remove workers that haven't been in contact for this many seconds - - /* Sometimes you want the block hashes even for shares that aren't block candidates. */ - "emitInvalidBlockHashes": false, - - /* Enable for client IP addresses to be detected when using a load balancer with TCP proxy - protocol enabled, such as HAProxy with 'send-proxy' param: - http://haproxy.1wt.eu/download/1.5/doc/configuration.txt */ - "tcpProxyProtocol": false, - - /* To receive payments, miners must connect with their address or mining key as their username. - This option will only authenticate miners using an address or mining key. */ - "validateWorkerUsername": true, - "paymentProcessing": { "enabled": true, @@ -379,50 +400,11 @@ Description of options: "daemon": { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } }, - /* Redis database used for storing share and block submission data and payment processing. */ - "redis": { - "host": "127.0.0.1", - "port": 6379 - }, - - /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want - to use the "emitInvalidBlockHashes" option below if you require it. The config options - "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ - "mposMode": { - "enabled": false, - "host": "127.0.0.1", //MySQL db host - "port": 3306, //MySQL db port - "user": "me", //MySQL db user - "password": "mypass", //MySQL db password - "database": "ltc", //MySQL db database name - - /* Checks for valid password in database when miners connect. */ - "checkPassword": true, - - /* Unregistered workers can automatically be registered (added to database) on stratum - worker authentication if this is true. */ - "autoCreateWorker": false - - - }, - - /* If a worker is submitting a high threshold of invalid shares we can temporarily ban their IP - to reduce system/network load. Also useful to fight against flooding attacks. If running - behind something like HAProxy be sure to enable 'tcpProxyProtocol', otherwise you'll end up - banning your own IP address (and therefore all workers). */ - "banning": { - "enabled": true, - "time": 600, //How many seconds to ban worker for - "invalidPercent": 50, //What percent of invalid shares triggers ban - "checkThreshold": 500, //Check invalid percent when this many shares have been submitted - "purgeInterval": 300 //Every this many seconds clear out the list of old bans - }, - /* Each pool can have as many ports for your miners to connect to as you wish. Each port can be configured to use its own pool difficulty and variable difficulty settings. varDiff is optional and will only be used for the ports you configure it for. */ @@ -445,24 +427,16 @@ Description of options: } }, - /* For redundancy, recommended to have at least two daemon instances running in case one - drops out-of-sync or offline. */ + /* More than one daemon instances can be setup in case one drops out-of-sync or dies. */ "daemons": [ { //Main daemon instance "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" - }, - { //Backup daemon instance - "host": "127.0.0.1", - "port": 19344, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } ], - /* This allows the pool to connect to the daemon as a node peer to receive block updates. It may be the most efficient way to get block updates (faster than polling, less intensive than blocknotify script). It requires the additional field "peerMagic" in @@ -480,6 +454,25 @@ Description of options: feature that prevents the daemon from spamming our peer node with unnecessary transaction data. Assume its supported but if you have problems try disabling it. */ "disableTransactions": true + }, + + /* Enabled this mode and shares will be inserted into in a MySQL database. You may also want + to use the "emitInvalidBlockHashes" option below if you require it. The config options + "redis" and "paymentProcessing" will be ignored/unused if this is enabled. */ + "mposMode": { + "enabled": false, + "host": "127.0.0.1", //MySQL db host + "port": 3306, //MySQL db port + "user": "me", //MySQL db user + "password": "mypass", //MySQL db password + "database": "ltc", //MySQL db database name + + /* Checks for valid password in database when miners connect. */ + "checkPassword": true, + + /* Unregistered workers can automatically be registered (added to database) on stratum + worker authentication if this is true. */ + "autoCreateWorker": false } } @@ -562,4 +555,4 @@ License ------- Released under the GNU General Public License v2 -http://www.gnu.org/licenses/gpl-2.0.html +http://www.gnu.org/licenses/gpl-2.0.html \ No newline at end of file diff --git a/config_example.json b/config_example.json index c473771..9534651 100644 --- a/config_example.json +++ b/config_example.json @@ -9,6 +9,26 @@ "forks": "auto" }, + "defaultPoolConfigs": { + "blockRefreshInterval": 1000, + "jobRebroadcastTimeout": 55, + "connectionTimeout": 600, + "emitInvalidBlockHashes": false, + "validateWorkerUsername": true, + "tcpProxyProtocol": false, + "banning": { + "enabled": true, + "time": 600, + "invalidPercent": 50, + "checkThreshold": 500, + "purgeInterval": 300 + }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + } + }, + "website": { "enabled": true, "host": "0.0.0.0", @@ -81,12 +101,5 @@ "usePoloniex": true, "useCryptsy": true, "useMintpal": true - }, - - "redisBlockNotifyListener": { - "enabled": false, - "redisPort": 6379, - "redisHost": "hostname", - "psubscribeKey": "newblocks:*" } } diff --git a/init.js b/init.js index 4ed6d90..f6e21cc 100644 --- a/init.js +++ b/init.js @@ -4,9 +4,10 @@ var os = require('os'); var cluster = require('cluster'); var async = require('async'); +var extend = require('extend'); + var PoolLogger = require('./libs/logUtil.js'); var CliListener = require('./libs/cliListener.js'); -var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); var Website = require('./libs/website.js'); @@ -145,6 +146,19 @@ var buildPoolConfigs = function(){ return; } + for (var option in portalConfig.defaultPoolConfigs){ + if (!(option in poolOptions)){ + var toCloneOption = portalConfig.defaultPoolConfigs[option]; + var clonedOption = {}; + if (toCloneOption.constructor === Object) + extend(true, clonedOption, toCloneOption); + else + clonedOption = toCloneOption; + poolOptions[option] = clonedOption; + } + } + + configs[poolOptions.coin.name] = poolOptions; if (!(coinProfile.algorithm in algos)){ @@ -334,24 +348,6 @@ var processCoinSwitchCommand = function(params, options, reply){ }; -var startRedisBlockListener = function(){ - //block notify options - //setup block notify here and use IPC to tell appropriate pools - - if (!portalConfig.redisBlockNotifyListener.enabled) return; - - var listener = new RedisBlocknotifyListener(portalConfig.redisBlockNotifyListener); - listener.on('log', function(text){ - logger.debug('Master', 'blocknotify', text); - }).on('hash', function (message) { - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - }); - listener.start(); -}; - var startPaymentProcessor = function(){ @@ -429,8 +425,6 @@ var startProfitSwitch = function(){ startPaymentProcessor(); - startRedisBlockListener(); - startWebsite(); startProfitSwitch(); diff --git a/libs/redisblocknotifyListener.js b/libs/redisblocknotifyListener.js deleted file mode 100644 index 05b9c7f..0000000 --- a/libs/redisblocknotifyListener.js +++ /dev/null @@ -1,36 +0,0 @@ -var events = require('events'); -var redis = require('redis'); - -var listener = module.exports = function listener(options){ - - var _this = this; - var redisConnection; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - redisConnection = redis.createClient(options.redisPort, options.redisHost); - redisConnection.on("pmessage", function (pattern, channel, message) { - var coinname = channel.split(':')[1]; - var blockhash = message; - //emitLog("Redis: Received block for "+coinname+" - hash: "+blockhash); - _this.emit('hash', { - "coin" : coinname, - "hash" : blockhash - }); - }); - redisConnection.on('connect', function (err, data) { - emitLog("Redis connected"); - }); - redisConnection.psubscribe(options.psubscribeKey); - emitLog("Connecting to redis!"); - } - - - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/package.json b/package.json index 133d279..f6467a2 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "node-watch": "*", "request": "*", "nonce": "*", - "bignum": "*" + "bignum": "*", + "extend": "*" }, "engines": { "node": ">=0.10" diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index ed5f414..f0647e0 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -6,19 +6,9 @@ "rewardRecipients": { "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, - "mirj3LtZxbSTharhtXvotqtJXUY7ki5qfx": 0.5, "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 }, - "blockRefreshInterval": 1000, - "jobRebroadcastTimeout": 55, - "connectionTimeout": 600, - "emitInvalidBlockHashes": false, - - "tcpProxyProtocol": false, - - "validateWorkerUsername": true, - "paymentProcessing": { "enabled": true, "paymentInterval": 20, @@ -26,38 +16,14 @@ "daemon": { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } }, - "redis": { - "host": "127.0.0.1", - "port": 6379 - }, - - "mposMode": { - "enabled": false, - "host": "127.0.0.1", - "port": 3306, - "user": "me", - "password": "mypass", - "database": "ltc", - "checkPassword": true, - "autoCreateWorker": false - }, - - "banning": { - "enabled": true, - "time": 300, - "invalidPercent": 50, - "checkThreshold": 10, - "purgeInterval": 300 - }, - "ports": { "3008": { - "diff": 4 + "diff": 8 }, "3032": { "diff": 32, @@ -78,8 +44,8 @@ { "host": "127.0.0.1", "port": 19332, - "user": "litecoinrpc", - "password": "testnet" + "user": "testuser", + "password": "testpass" } ], @@ -88,6 +54,17 @@ "host": "127.0.0.1", "port": 19333, "disableTransactions": true + }, + + "mposMode": { + "enabled": false, + "host": "127.0.0.1", + "port": 3306, + "user": "me", + "password": "mypass", + "database": "ltc", + "checkPassword": true, + "autoCreateWorker": false } } \ No newline at end of file From 04f769a5a66dadc680c490257c02f988d013c39a Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 11:59:22 -0600 Subject: [PATCH 40/46] Add credit to Fcases (triplef) for ordering me a pizza! --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b87889b..ec048e2 100644 --- a/README.md +++ b/README.md @@ -548,6 +548,7 @@ Credits * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script * [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode * [icecube45](//github.com/icecube45) - helping out with the repo wiki +* [Fcases](//github.com/Fcases) - ordered me a pizza <3 * Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) From e6556d416ecb63b3dbcf63939d8e54401973b54d Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 17:43:11 -0600 Subject: [PATCH 41/46] [REDIS BREAKING UPDATE] Changed coins to use redis branching for cleaner management --- libs/paymentProcessor.js | 24 ++++++++++++------------ libs/shareProcessor.js | 16 ++++++++-------- libs/stats.js | 12 ++++++------ 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 2fd2fad..8e7866e 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -152,8 +152,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ startRedisTimer(); redisClient.multi([ - ['hgetall', coin + '_balances'], - ['smembers', coin + '_blocksPending'] + ['hgetall', coin + ':balances'], + ['smembers', coin + ':blocksPending'] ]).exec(function(error, results){ endRedisTimer(); @@ -294,7 +294,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var shareLookups = rounds.map(function(r){ - return ['hgetall', coin + '_shares:round' + r.height] + return ['hgetall', coin + ':shares:round' + r.height] }); startRedisTimer(); @@ -419,13 +419,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ if (worker.balanceChange !== 0){ balanceUpdateCommands.push([ 'hincrbyfloat', - coin + '_balances', + coin + ':balances', w, satoshisToCoins(worker.balanceChange) ]); } if (worker.sent !== 0){ - workerPayoutsCommand.push(['hincrbyfloat', coin + '_payouts', w, worker.sent]); + workerPayoutsCommand.push(['hincrbyfloat', coin + ':payouts', w, worker.sent]); totalPaid += worker.sent; } } @@ -439,7 +439,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ var moveSharesToCurrent = function(r){ var workerShares = r.workerShares; Object.keys(workerShares).forEach(function(worker){ - orphanMergeCommands.push(['hincrby', coin + '_shares:roundCurrent', + orphanMergeCommands.push(['hincrby', coin + ':shares:roundCurrent', worker, workerShares[worker]]); }); }; @@ -448,17 +448,17 @@ function SetupForPool(logger, poolOptions, setupFinished){ switch(r.category){ case 'kicked': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksKicked', r.serialized]); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksKicked', r.serialized]); case 'orphan': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksOrphaned', r.serialized]); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksOrphaned', r.serialized]); if (r.canDeleteShares){ moveSharesToCurrent(r); - roundsToDelete.push(coin + '_shares:round' + r.height); + roundsToDelete.push(coin + ':shares:round' + r.height); } return; case 'generate': - movePendingCommands.push(['smove', coin + '_blocksPending', coin + '_blocksConfirmed', r.serialized]); - roundsToDelete.push(coin + '_shares:round' + r.height); + movePendingCommands.push(['smove', coin + ':blocksPending', coin + ':blocksConfirmed', r.serialized]); + roundsToDelete.push(coin + ':shares:round' + r.height); return; } @@ -482,7 +482,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ finalRedisCommands.push(['del'].concat(roundsToDelete)); if (totalPaid !== 0) - finalRedisCommands.push(['hincrbyfloat', coin + '_stats', 'totalPaid', totalPaid]); + finalRedisCommands.push(['hincrbyfloat', coin + ':stats', 'totalPaid', totalPaid]); if (finalRedisCommands.length === 0){ callback(); diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index aeeda49..9dface3 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -45,27 +45,27 @@ module.exports = function(logger, poolConfig){ var redisCommands = []; if (isValidShare){ - redisCommands.push(['hincrbyfloat', coin + '_shares:roundCurrent', shareData.worker, shareData.difficulty]); - redisCommands.push(['hincrby', coin + '_stats', 'validShares', 1]); + redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); + redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); /* 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 = [shareData.difficulty, shareData.worker, dateNow]; - redisCommands.push(['zadd', coin + '_hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); + redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); } else{ - redisCommands.push(['hincrby', coin + '_stats', 'invalidShares', 1]); + redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); } if (isValidBlock){ - redisCommands.push(['rename', coin + '_shares:roundCurrent', coin + '_shares:round' + shareData.height]); - redisCommands.push(['sadd', coin + '_blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); - redisCommands.push(['hincrby', coin + '_stats', 'validBlocks', 1]); + redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + '_shares:round' + shareData.height]); + redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); + redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); } else if (shareData.blockHash){ - redisCommands.push(['hincrby', coin + '_stats', 'invalidBlocks', 1]); + redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]); } connection.multi(redisCommands).exec(function(err, replies){ diff --git a/libs/stats.js b/libs/stats.js index 75cfc91..977002e 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -109,12 +109,12 @@ module.exports = function(logger, portalConfig, poolConfigs){ var redisCommandTemplates = [ - ['zremrangebyscore', '_hashrate', '-inf', '(' + windowTime], - ['zrangebyscore', '_hashrate', windowTime, '+inf'], - ['hgetall', '_stats'], - ['scard', '_blocksPending'], - ['scard', '_blocksConfirmed'], - ['scard', '_blocksOrphaned'] + ['zremrangebyscore', ':hashrate', '-inf', '(' + windowTime], + ['zrangebyscore', ':hashrate', windowTime, '+inf'], + ['hgetall', ':stats'], + ['scard', ':blocksPending'], + ['scard', ':blocksConfirmed'], + ['scard', ':blocksOrphaned'] ]; var commandsPerCoin = redisCommandTemplates.length; From 936b723c6665401a7b7c16847ba0ea31a6b2eefe Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 17:48:19 -0600 Subject: [PATCH 42/46] Missed an underscore for last commit --- libs/shareProcessor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/shareProcessor.js b/libs/shareProcessor.js index 9dface3..2632174 100644 --- a/libs/shareProcessor.js +++ b/libs/shareProcessor.js @@ -60,7 +60,7 @@ module.exports = function(logger, poolConfig){ } if (isValidBlock){ - redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + '_shares:round' + shareData.height]); + redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); } From e08d313b1a80f91c2c876c6a3a6351a01102ba8c Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:31:38 -0600 Subject: [PATCH 43/46] Added redis security warning --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ec048e2..912eaa4 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,8 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing +[*Redis security warning*](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file + Clone the repository and run `npm update` for all the dependencies to be installed: ```bash From 6681bf3de66bc6d9079920d080d1f619977ef7ea Mon Sep 17 00:00:00 2001 From: Nicholas Orr Date: Sat, 10 May 2014 08:31:59 +0800 Subject: [PATCH 44/46] Allow ability to shed sudo --- init.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/init.js b/init.js index f6e21cc..75bdc61 100644 --- a/init.js +++ b/init.js @@ -51,6 +51,15 @@ try{ if (cluster.isMaster) logger.warning('POSIX', 'Connection Limit', '(Safe to ignore) Must be ran as root to increase resource limits'); } + finally { + // Find out which user used sudo through the environment variable + var uid = parseInt(process.env.SUDO_UID); + // Set our server's uid to that user + if (uid) { + process.setuid(uid); + logger.debug('POSIX', 'Connection Limit', 'Raised to 100K concurrent connections, now running as non-root user: ' + process.getuid()); + } + } } catch(e){ if (cluster.isMaster) From a63a206ff68b5b64ea8f503f761d2071f9293566 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:32:54 -0600 Subject: [PATCH 45/46] Added redis security warning --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 912eaa4..2efd64f 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing -[*Redis security warning*](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file +[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file Clone the repository and run `npm update` for all the dependencies to be installed: From 2eef180751bd2238274df4629065fcc96b3d13e2 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 9 May 2014 18:36:27 -0600 Subject: [PATCH 46/46] Added redis security warning --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2efd64f..c7bd804 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ a good pool operator. For starters be sure to read: #### 1) Downloading & Installing -[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis, and easy way is to include `bind 127.0.0.1` in your `redis.conf` file +[**Redis security warning**](http://redis.io/topics/security): be sure firewall access to redis - an easy way is to include `bind 127.0.0.1` in your `redis.conf` file Clone the repository and run `npm update` for all the dependencies to be installed: