diff --git a/README.md b/README.md index fd11497..8be38b8 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,34 @@ #### Node Open Mining Portal This portal is an extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool written -entirely in Node.js. It contains a stratum poolserver, reward/payment/share processor, and a (*not yet completed*) -front-end website. +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. -#### Features +#### Table of Contents +* [Features](#features) + * [Attack Mitigation](##attack-mitigation) + * [Security](#security) + * [Planned Features](#planned-features) +* [Community Support](#community--support) +* [Usage](#usage) + * [Requirements](#requirements) + * [Setting Up Coin Daemon](#0-setting-up-coin-daemon) + * [Downloading & Installing](#1-downloading--installing) + * [Configuration](#2-configuration) + * [Portal Config](#portal-config) + * [Coin Config](#coin-config) + * [Pool Config](#pool-config) + * [Setting Up Blocknotify](#optional-recommended-setting-up-blocknotify) + * [Starting the Portal](#3-start-the-portal) + * [Upgrading NOMP](#upgrading-nomp) +* [Donations](#donations) +* [Credits](#credits) +* [License](#license) + + + + +### Features * For the pool server it uses the highly efficient [node-stratum-pool](//github.com/zone117x/node-stratum-pool) module which supports vardiff, POW & POS, transaction messages, anti-DDoS, IP banning, [several hashing algorithms](//github.com/zone117x/node-stratum-pool#hashing-algorithms-supported). @@ -13,6 +37,7 @@ supports vardiff, POW & POS, transaction messages, anti-DDoS, IP banning, [sever * The portal has an [MPOS](//github.com/MPOS/php-mpos) compatibility mode so that the it can function as a drop-in-replacement for [python-stratum-mining](//github.com/Crypto-Expert/stratum-mining). This mode can be enabled in the configuration and will insert shares into a MySQL database in the format which MPOS expects. +For a direct tutorial see the wiki page [Setting up NOMP for MPOS usage](//github.com/zone117x/node-open-mining-portal/wiki/Setting-up-NOMP-for-MPOS-usage). * Multi-pool ability - this software was built from the ground up to run with multiple coins simultaneously (which can have different properties and hashing algorithms). It can be used to create a pool for a single coin or for multiple @@ -64,7 +89,7 @@ allow your own pool to connect upstream to a larger pool server. It will request redistribute the work to our own connected miners. -#### Community / Support +### Community / Support IRC * Support / general discussion join #nomp: https://webchat.freenode.net/?channels=#nomp * Development discussion join #nomp-dev: https://webchat.freenode.net/?channels=#nomp-dev @@ -80,8 +105,11 @@ If your pool uses NOMP let us know and we will list your website here. * http://chunkypools.com * http://clevermining.com * http://rapidhash.net +* http://suchpool.pw * http://hashfaster.com +* http://miningpoolhub.com * http://kryptochaos.com +* http://pool.uberpools.org Usage @@ -93,6 +121,8 @@ Usage * [Node.js](http://nodejs.org/) v0.10+ ([follow these installation instructions](https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager)) * [Redis](http://redis.io/) key-value store v2.6+ ([follow these instructions](http://redis.io/topics/quickstart)) +##### Seriously +Those are legitimate requirements. If you use old versions of Node.js or Redis that may come with your system package manager then you will have problems. Follow the linked instructions to get the last stable versions. #### 0) Setting up coin daemon Follow the build/install instructions for your coin daemon. Your coin.conf file should end up looking something like this: @@ -156,12 +186,7 @@ Explanation for each field: /* How many seconds to hold onto historical stats. Currently set to 24 hours. */ "historicalRetention": 43200, /* How many seconds worth of shares should be gathered to generate hashrate. */ - "hashrateWindow": 300, - /* Redis instance of where to store historical stats. */ - "redis": { - "host": "localhost", - "port": 6379 - } + "hashrateWindow": 300 }, /* Not done yet. */ "adminCenter": { @@ -170,6 +195,13 @@ Explanation for each field: } }, + /* Redis instance of where to store global portal data such as historical stats, proxy states, + ect.. */ + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + /* With this enabled, the master process listen on the configured port for messages from the 'scripts/blockNotify.js' script which your coin daemons can be configured to run when a new block is available. When a blocknotify message is received, the master process uses @@ -291,6 +323,12 @@ Description of options: due to mining apps using incorrect max diffs and this pool using correct max diffs. */ "shareVariancePercent": 10, + /* 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, + + /* 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. @@ -339,7 +377,7 @@ Description of options: configured 'address' that receives the block rewards, otherwise the daemon will not be able to confirm blocks or send out payments. */ "daemon": { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" @@ -347,7 +385,7 @@ Description of options: /* Redis database used for storing share and block submission data. */ "redis": { - "host": "localhost", + "host": "127.0.0.1", "port": 6379 } }, @@ -356,7 +394,7 @@ Description of options: also want to use the "emitInvalidBlockHashes" option below if you require it. */ "mpos": { "enabled": false, - "host": "localhost", //MySQL db host + "host": "127.0.0.1", //MySQL db host "port": 3306, //MySQL db port "user": "me", //MySQL db user "password": "mypass", //MySQL db password @@ -369,8 +407,10 @@ Description of options: } }, - /* If a worker is submitting a high threshold of invalid shares we can temporarily ban them - to reduce system/network load. Also useful to fight against flooding attacks. */ + /* 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 @@ -405,13 +445,13 @@ Description of options: drops out-of-sync or offline. */ "daemons": [ { //Main daemon instance - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, { //Backup daemon instance - "host": "localhost", + "host": "127.0.0.1", "port": 19344, "user": "litecoinrpc", "password": "testnet" @@ -419,23 +459,29 @@ Description of options: ], - /* This allows the pool to connect to the daemon as a node peer to recieve block updates. + /* 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). However its still under development (not yet working). */ + intensive than blocknotify script). It requires additional setup: the 'magic' field must + be exact (extracted from the coin source code). */ "p2p": { "enabled": false, - "host": "localhost", + + /* Host for daemon */ + "host": "127.0.0.1", + + /* Port configured for daemon (this is the actual peer port not RPC port) */ "port": 19333, + /* 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", - - //Found in src as the PROTOCOL_VERSION variable, for example: http://git.io/KjuCrw - "protocolVersion": 70002, + And for litecoin testnet magic: http://git.io/NXBYJA */ + "magic": "fcc1b7dc" } } @@ -456,7 +502,7 @@ node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener ``` Example: inside `dogecoin.conf` add the line ``` -blocknotify="node scripts/blockNotify.js localhost:8117 mySuperSecurePassword dogecoin %s" +blocknotify=node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s ``` Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions @@ -503,11 +549,14 @@ Credits ------- * [Jerry Brady / mintyfresh68](https://github.com/bluecircle) - got coin-switching fully working and developed proxy-per-algo feature * [Tony Dobbs](http://anthonydobbs.com) - designs for front-end and created the NOMP logo +* [LucasJones(//github.com/LucasJones) - getting p2p block notify script working * [vekexasia](//github.com/vekexasia) - co-developer & great tester * [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing * [Alex Petrov / sysmanalex](https://github.com/sysmanalex) - contributed the pure C block notify script -* Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool) +* [svirusxxx](//github.com/svirusxxx) - sponsored development of MPOS mode +* [icecube45](//github.com/icecube45) - helping out with the repo wiki +* Those that contributed to [node-stratum-pool](//github.com/zone117x/node-stratum-pool#credits) License diff --git a/coins/copperlark.json b/coins/copperlark.json new file mode 100644 index 0000000..7f07f12 --- /dev/null +++ b/coins/copperlark.json @@ -0,0 +1,7 @@ +{ + "name": "Copperlark", + "symbol": "CLR", + "algorithm": "keccak", + "normalHashing": true, + "diffShift": 32 +} \ No newline at end of file diff --git a/coins/cryptometh.json b/coins/cryptometh.json new file mode 100644 index 0000000..6015361 --- /dev/null +++ b/coins/cryptometh.json @@ -0,0 +1,5 @@ +{ + "name": "Cryptometh", + "symbol": "METH", + "algorithm": "keccak" +} \ No newline at end of file diff --git a/coins/ecoin.json b/coins/ecoin.json new file mode 100644 index 0000000..9703edf --- /dev/null +++ b/coins/ecoin.json @@ -0,0 +1,7 @@ +{ + "name": "Ecoin", + "symbol": "ECN", + "algorithm": "keccak", + "normalHashing": true, + "diffShift": 32 +} \ No newline at end of file diff --git a/config_example.json b/config_example.json index 0ddaa74..5a47449 100644 --- a/config_example.json +++ b/config_example.json @@ -13,11 +13,7 @@ "stats": { "updateInterval": 60, "historicalRetention": 43200, - "hashrateWindow": 300, - "redis": { - "host": "localhost", - "port": 6379 - } + "hashrateWindow": 300 }, "adminCenter": { "enabled": true, @@ -25,6 +21,11 @@ } }, + "redis": { + "host": "127.0.0.1", + "port": 6379 + }, + "blockNotifyListener": { "enabled": false, "port": 8117, diff --git a/init.js b/init.js index 95f5a99..a7b15b6 100644 --- a/init.js +++ b/init.js @@ -8,7 +8,6 @@ var PoolLogger = require('./libs/logUtil.js'); var BlocknotifyListener = require('./libs/blocknotifyListener.js'); var CoinswitchListener = require('./libs/coinswitchListener.js'); var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); -var WorkerListener = require('./libs/workerListener.js'); var PoolWorker = require('./libs/poolWorker.js'); var PaymentProcessor = require('./libs/paymentProcessor.js'); var Website = require('./libs/website.js'); @@ -143,6 +142,7 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ return portalConfig.clustering.forks; })(); + var poolWorkers = {}; var createPoolWorker = function(forkId){ var worker = cluster.fork({ @@ -151,11 +151,24 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ pools: serializedConfigs, portalConfig: JSON.stringify(portalConfig) }); + worker.forkId = forkId; + worker.type = 'pool'; + poolWorkers[forkId] = worker; worker.on('exit', function(code, signal){ logger.error('Master', 'PoolSpanwer', 'Fork ' + forkId + ' died, spawning replacement worker...'); setTimeout(function(){ createPoolWorker(forkId); }, 2000); + }).on('message', function(msg){ + switch(msg.type){ + case 'banIP': + Object.keys(cluster.workers).forEach(function(id) { + if (cluster.workers[id].type === 'pool'){ + cluster.workers[id].send({type: 'banIP', ip: msg.ip}); + } + }); + break; + } }); }; @@ -172,12 +185,6 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ }; -var startWorkerListener = function(poolConfigs){ - var workerListener = new WorkerListener(logger, poolConfigs); - workerListener.init(); -}; - - var startBlockListener = function(portalConfig){ //block notify options //setup block notify here and use IPC to tell appropriate pools @@ -323,8 +330,6 @@ var startProfitSwitch = function(portalConfig, poolConfigs){ startRedisBlockListener(portalConfig); - startWorkerListener(poolConfigs); - startWebsite(portalConfig, poolConfigs); startProfitSwitch(portalConfig, poolConfigs); diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 2ee3f19..659d290 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -41,7 +41,7 @@ module.exports = function(logger, poolConfig){ connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', - [workerName], + [workerName.toLowerCase()], function(err, result){ if (err){ logger.error(logIdentify, logComponent, 'Database error when authenticating worker: ' + diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 71da314..69d130f 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -22,6 +22,13 @@ module.exports = function(logger){ process.on('message', function(message) { switch(message.type){ + case 'banIP': + for (var p in pools){ + if (pools[p].stratumServer) + pools[p].stratumServer.addBannedIP(message.ip); + } + break; + case 'blocknotify': var messageCoin = message.coin.toLowerCase(); @@ -30,7 +37,7 @@ module.exports = function(logger){ })[0]; if (poolTarget) - pools[poolTarget].processBlockNotify(message.hash); + pools[poolTarget].processBlockNotify(message.hash, 'blocknotify script'); break; @@ -75,7 +82,7 @@ module.exports = function(logger){ ); proxySwitch[algo].currentPool = newCoin; - var redisClient = redis.createClient(6379, "localhost") + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) redisClient.on('ready', function(){ redisClient.hset('proxyState', algo, newCoin, function(error, obj) { if (error) { @@ -186,6 +193,8 @@ module.exports = function(logger){ handlers.diff(workerName, diff); }).on('log', function(severity, text) { logger[severity](logSystem, logComponent, logSubCat, text); + }).on('banIP', function(ip, worker){ + process.send({type: 'banIP', ip: ip}); }); pool.start(); @@ -206,7 +215,7 @@ module.exports = function(logger){ // on the last pool it was using when reloaded or restarted // logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); - var redisClient = redis.createClient(6379, "localhost") + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) redisClient.on('ready', function(){ redisClient.hgetall("proxyState", function(error, obj) { if (error || obj == null) { diff --git a/libs/stats.js b/libs/stats.js index 62b7f00..0893506 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -59,7 +59,7 @@ module.exports = function(logger, portalConfig, poolConfigs){ function setupStatsRedis(){ - redisStats = redis.createClient(portalConfig.website.stats.redis.port, portalConfig.website.stats.redis.host); + redisStats = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); redisStats.on('error', function(err){ logger.error(logSystem, 'Historics', 'Redis for stats had an error ' + JSON.stringify(err)); }); diff --git a/libs/workerListener.js b/libs/workerListener.js deleted file mode 100644 index 6bb669b..0000000 --- a/libs/workerListener.js +++ /dev/null @@ -1,26 +0,0 @@ -var events = require('events'); -var cluster = require('cluster'); - -var MposCompatibility = require('./mposCompatibility.js'); -var ShareProcessor = require('./shareProcessor.js'); - - -var processor = module.exports = function processor(logger, poolConfigs){ - - var _this = this; - - - this.init = function(){ - - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].on('message', function(data){ - switch(data.type){ - - } - }); - }); - } -}; - - -processor.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/pool_configs/litecoin_example.json b/pool_configs/litecoin_example.json index a753c31..7f75c58 100644 --- a/pool_configs/litecoin_example.json +++ b/pool_configs/litecoin_example.json @@ -10,6 +10,8 @@ "emitInvalidBlockHashes": false, "shareVariancePercent": 15, + "tcpProxyProtocol": false, + "shareProcessing": { "internal": { "enabled": true, @@ -22,19 +24,19 @@ "feeReceiveAddress": "mppaGeNaSbG1Q7S6V3gL5uJztMhucgL9Vh", "feeWithdrawalThreshold": 5, "daemon": { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, "redis": { - "host": "localhost", + "host": "127.0.0.1", "port": 6379 } }, "mpos": { "enabled": false, - "host": "localhost", + "host": "127.0.0.1", "port": 3306, "user": "me", "password": "mypass", @@ -72,13 +74,13 @@ "daemons": [ { - "host": "localhost", + "host": "127.0.0.1", "port": 19332, "user": "litecoinrpc", "password": "testnet" }, { - "host": "localhost", + "host": "127.0.0.1", "port": 19344, "user": "litecoinrpc", "password": "testnet" @@ -87,9 +89,9 @@ "p2p": { "enabled": false, - "host": "localhost", + "host": "127.0.0.1", "port": 19333, - "protocolVersion": 70002, + "disableTransactions": true, "magic": "fcc1b7dc" } } diff --git a/scripts/blockNotify.js b/scripts/blockNotify.js index 67ed235..42a0f8c 100644 --- a/scripts/blockNotify.js +++ b/scripts/blockNotify.js @@ -1,6 +1,6 @@ /* This script should be hooked to the coin daemon as follow: - litecoind -blocknotify="node /path/to/this/script/blockNotify.js localhost:8117 password litecoin %s" + litecoind -blocknotify="node /path/to/this/script/blockNotify.js 127.0.0.1:8117 password litecoin %s" The above will send tell litecoin to launch this script with those parameters every time a block is found. This script will then send the blockhash along with other information to a listening tcp socket */ diff --git a/scripts/coinSwitch.js b/scripts/coinSwitch.js index 18dc1c8..acf6ac1 100644 --- a/scripts/coinSwitch.js +++ b/scripts/coinSwitch.js @@ -2,7 +2,7 @@ This script demonstrates sending a coin switch request and can be invoked from the command line with: - "node coinSwitch.js localhost:8118 password %s" + "node coinSwitch.js 127.0.0.1:8118 password %s" where <%s> is the name of the coin proxy miners will be switched onto.