diff --git a/.gitignore b/.gitignore index 88d9a09..b2f46e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ .idea/ -config.json \ No newline at end of file +config.json +pool_configs/*.json +!pool_configs/litecoin_example.json diff --git a/README.md b/README.md index 8be38b8..eeb7214 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ responsive user-friendly front-end website featuring mining instructions, in-dep #### Table of Contents * [Features](#features) - * [Attack Mitigation](##attack-mitigation) + * [Attack Mitigation](#attack-mitigation) * [Security](#security) * [Planned Features](#planned-features) * [Community Support](#community--support) @@ -45,17 +45,15 @@ coins at once. The pools use clustering to load balance across multiple CPU core * For reward/payment processing, shares are inserted into Redis (a fast NoSQL key/value store). The PROP (proportional) reward system is used with [Redis Transactions](http://redis.io/topics/transactions) for secure and super speedy payouts. -Each and every share will be rewarded - even for rounds resulting in orphaned blocks. +There is zero risk to the pool operator. Shares from rounds resulting in orphaned blocks will be merged into share in the +current round so that each and every share will be rewarded * This portal does not have user accounts/logins/registrations. Instead, miners simply use their coin address for stratum authentication. A minimalistic HTML5 front-end connects to the portals statistics API to display stats from from each pool such as connected miners, network/pool difficulty/hash rate, etc. -* Automated switching of connected miners to different pools/coins is also easily done due to the multi-pool architecture -of this software. To use this feature the switching must be controlled by your own script, such as one that calculates -coin profitability via an API such as CoinChoose.com or CoinWarz.com (or calculated locally using daemon-reported network -difficulties and exchange APIs). NOMP's regular payment processing and miner authentication which using coin address as stratum -username will obviously not work with this coin switching feature - so you must control those with your own script as well. +* Coin-switching ports using coin-networks and crypto-exchange APIs to detect profitability. Miner's connect to these ports +with their public key which NOMP uses to derive an address for any coin needed to be paid out. #### Attack Mitigation @@ -108,9 +106,15 @@ If your pool uses NOMP let us know and we will list your website here. * http://suchpool.pw * http://hashfaster.com * http://miningpoolhub.com +* http://teamdoge.com +* http://miningwith.us * http://kryptochaos.com -* http://pool.uberpools.org - +* http://uberpools.org +* http://onebtcplace.com +* http://minr.es +* http://mining.theminingpools.com +* http://www.omargpools.ca/pools.html +* http://pool.trademybit.com/ Usage ===== @@ -163,7 +167,12 @@ 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" - + + + /* The NOMP CLI (command-line interface) will listen for commands on this port. For example, + blocknotify messages are sent to NOMP through this. */ + "cliPort": 17117, + /* By default 'forks' is set to "auto" which will spawn one process/fork/worker for each CPU core in your system. Each of these workers will run a separate instance of your pool(s), and the kernel will load balance miners using these forks. Optionally, the 'forks' field @@ -202,65 +211,67 @@ Explanation for each field: "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 - IPC (inter-process communication) to notify each thread about the message. Each thread - then sends the message to the appropriate coin pool. See "Setting up blocknotify" below to - set up your daemon to use this feature. */ - "blockNotifyListener": { - "enabled": true, - "port": 8117, - "password": "test" - }, - - /* With this enabled, the master process will listen on the configured port for messages from - the 'scripts/coinSwitch.js' script which will trigger your proxy pools to switch to the - specified coin (non-case-sensitive). This setting is used in conjuction with the proxy - feature below. */ - "coinSwitchListener": { - "enabled": false, - "port": 8118, - "password": "test" - }, - /* In a proxy configuration, you can setup ports that accept miners for work based on a - specific algorithm instead of a specific coin. Miners that connect to these ports are + /* With this switching configuration, you can setup ports that accept miners for work based on + a specific algorithm instead of a specific coin. Miners that connect to these ports are automatically switched a coin determined by the server. The default coin is the first configured pool for each algorithm and coin switching can be triggered using the - coinSwitch.js script in the scripts folder. + cli.js script in the scripts folder. - Please note miner address authentication must be disabled when using NOMP in a proxy - configuration and that payout processing is left up to the server administrator. */ - "proxy": { - "sha256": { + Miners connecting to these switching ports must use their public key in the format of + RIPEMD160(SHA256(public-key)). An address for each type of coin is derived from the miner's + public key, and payments are sent to that address. */ + "switching": { + "switch1": { "enabled": false, - "port": "3333", - "diff": 10, - "varDiff": { - "minDiff": 16, //Minimum difficulty - "maxDiff": 512, //Network difficulty will be used if it is lower than this - "targetTime": 15, //Try to get 1 share per this many seconds - "retargetTime": 90, //Check to see if we should retarget every this many seconds - "variancePercent": 30 //Allow time to very this % from target without retargeting + "algorithm": "sha256", + "ports": { + "3333": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt": { + "switch2": { "enabled": false, - "port": "4444", - "diff": 10, - "varDiff": { - "minDiff": 16, //Minimum difficulty - "maxDiff": 512, //Network difficulty will be used if it is lower than this - "targetTime": 15, //Try to get 1 share per this many seconds - "retargetTime": 90, //Check to see if we should retarget every this many seconds - "variancePercent": 30 //Allow time to very this % from target without retargeting + "algorithm": "scrypt", + "ports": { + "4444": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt-n": { + "switch3": { "enabled": false, - "port": "5555" + "algorithm": "x11", + "ports": { + "5555": { + "diff": 0.001 + } + } } + }, + + "profitSwitch": { + "enabled": false, + "updateInterval": 600, + "depth": 0.90, + "usePoloniex": true, + "useCryptsy": true, + "useMintpal": true } } ```` @@ -302,8 +313,8 @@ Description of options: job broadcast. */ "txRefreshInterval": 20000, - /* Some miner software is bugged and will consider the pool offline if it doesn't receive - anything for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast + /* 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, @@ -400,6 +411,10 @@ Description of options: "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 */ @@ -498,11 +513,11 @@ For more information on these configuration options see the [pool module documen 1. In `config.json` set the port and password for `blockNotifyListener` 2. In your daemon conf file set the `blocknotify` command to use: ``` -node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener password] [coin name in config] %s +node [path to cli.js] [coin name in config] [block hash symbol] ``` Example: inside `dogecoin.conf` add the line ``` -blocknotify=node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s +blocknotify=node /home/nomp/scripts/cli.js blocknotify dogecoin %s ``` Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions @@ -526,7 +541,8 @@ output from NOMP. #### Upgrading NOMP -When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update the `node-statum-pool` module and any config files that may have been changed. +When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update +the `node-statum-pool` and `node-multi-hashing` modules, and any config files that may have been changed. * Inside your NOMP directory (where the init.js script is) do `git pull` to get the latest NOMP code. * Remove the dependenices by deleting the `node_modules` directory with `rm -r node_modules`. * Run `npm update` to force updating/reinstalling of the dependencies. @@ -549,7 +565,7 @@ 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 +* [LucasJones](//github.com/LucasJones) - got p2p block notify working and implemented additional hashing algos * [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 diff --git a/coins/asiccoin.json b/coins/asiccoin.json new file mode 100644 index 0000000..3c039b9 --- /dev/null +++ b/coins/asiccoin.json @@ -0,0 +1,5 @@ +{ + "name": "ASICcoin", + "symbol": "ASC", + "algorithm": "sha256" +} diff --git a/coins/battlecoin.json b/coins/battlecoin.json new file mode 100644 index 0000000..1e7fd9e --- /dev/null +++ b/coins/battlecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Battlecoin", + "symbol": "BCX", + "algorithm": "sha256" +} diff --git a/coins/benjamins.json b/coins/benjamins.json new file mode 100644 index 0000000..e73a1b6 --- /dev/null +++ b/coins/benjamins.json @@ -0,0 +1,5 @@ +{ + "name": "Benjamins", + "symbol": "BEN", + "algorithm": "sha256" +} diff --git a/coins/betacoin.json b/coins/betacoin.json new file mode 100644 index 0000000..b98efcd --- /dev/null +++ b/coins/betacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Betacoin", + "symbol": "BET", + "algorithm": "sha256" +} diff --git a/coins/devcoin.json b/coins/devcoin.json new file mode 100644 index 0000000..fcbf0f0 --- /dev/null +++ b/coins/devcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Devcoin", + "symbol": "DVC", + "algorithm": "sha256" +} diff --git a/coins/ecoin.json b/coins/ecoin.json deleted file mode 100644 index 9703edf..0000000 --- a/coins/ecoin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Ecoin", - "symbol": "ECN", - "algorithm": "keccak", - "normalHashing": true, - "diffShift": 32 -} \ No newline at end of file diff --git a/coins/emark.json b/coins/emark.json new file mode 100644 index 0000000..d14cd3e --- /dev/null +++ b/coins/emark.json @@ -0,0 +1,5 @@ +{ + "name": "eMark", + "symbol": "DEM", + "algorithm": "sha256" +} diff --git a/coins/fedoracoin.json b/coins/fedoracoin.json new file mode 100644 index 0000000..a383262 --- /dev/null +++ b/coins/fedoracoin.json @@ -0,0 +1,5 @@ +{ + "name": "FedoraCoin", + "symbol": "TiPS", + "algorithm": "scrypt" +} diff --git a/coins/fireflycoin.json b/coins/fireflycoin.json new file mode 100644 index 0000000..98c3eb6 --- /dev/null +++ b/coins/fireflycoin.json @@ -0,0 +1,5 @@ +{ + "name": "Fireflycoin", + "symbol": "FFC", + "algorithm": "sha256" +} diff --git a/coins/freicoin.json b/coins/freicoin.json new file mode 100644 index 0000000..8bf60d9 --- /dev/null +++ b/coins/freicoin.json @@ -0,0 +1,5 @@ +{ + "name": "Freicoin", + "symbol": "FRC", + "algorithm": "sha256" +} diff --git a/coins/giarcoin.json b/coins/giarcoin.json new file mode 100644 index 0000000..e0f592d --- /dev/null +++ b/coins/giarcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Giarcoin", + "symbol": "GIAR", + "algorithm": "scrypt-n" +} diff --git a/coins/globalboost.json b/coins/globalboost.json new file mode 100644 index 0000000..8114798 --- /dev/null +++ b/coins/globalboost.json @@ -0,0 +1,5 @@ +{ + "name": "GlobalBoost", + "symbol": "BST", + "algorithm": "scrypt" +} diff --git a/coins/globaldenomination.json b/coins/globaldenomination.json new file mode 100644 index 0000000..d43c28b --- /dev/null +++ b/coins/globaldenomination.json @@ -0,0 +1,5 @@ +{ + "name": "GlobalDenomination", + "symbol": "GDN", + "algorithm": "x11" +} diff --git a/coins/guarantcoin.json b/coins/guarantcoin.json new file mode 100644 index 0000000..9c3c114 --- /dev/null +++ b/coins/guarantcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Guarantcoin", + "symbol": "GTC", + "algorithm": "scrypt-n" +} diff --git a/coins/ixcoin.json b/coins/ixcoin.json new file mode 100644 index 0000000..80ad2eb --- /dev/null +++ b/coins/ixcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Ixcoin", + "symbol": "IXC", + "algorithm": "sha256" +} diff --git a/coins/joulecoin.json b/coins/joulecoin.json new file mode 100644 index 0000000..1500432 --- /dev/null +++ b/coins/joulecoin.json @@ -0,0 +1,5 @@ +{ + "name": "Joulecoin", + "symbol": "XJO", + "algorithm": "sha256" +} diff --git a/coins/mazacoin.json b/coins/mazacoin.json new file mode 100644 index 0000000..451738c --- /dev/null +++ b/coins/mazacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Mazacoin", + "symbol": "MZC", + "algorithm": "sha256" +} diff --git a/coins/mintcoin.json b/coins/mintcoin.json new file mode 100644 index 0000000..fb93cfb --- /dev/null +++ b/coins/mintcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Mintcoin", + "symbol": "MINT", + "algorithm": "scrypt" +} \ No newline at end of file diff --git a/coins/opensourcecoin.json b/coins/opensourcecoin.json new file mode 100644 index 0000000..9b86d03 --- /dev/null +++ b/coins/opensourcecoin.json @@ -0,0 +1,5 @@ +{ + "name": "OpenSourcecoin", + "symbol": "OSC", + "algorithm": "sha256" +} diff --git a/coins/quarkcoin.json b/coins/quarkcoin.json index 797e7cd..983f340 100644 --- a/coins/quarkcoin.json +++ b/coins/quarkcoin.json @@ -1,5 +1,6 @@ { "name": "Quarkcoin", "symbol": "QRK", - "algorithm": "quark" -} \ No newline at end of file + "algorithm": "quark", + "mposDiffMultiplier": 256 +} diff --git a/coins/starcoin.json b/coins/starcoin.json new file mode 100644 index 0000000..ca65a81 --- /dev/null +++ b/coins/starcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Starcoin", + "symbol": "STR", + "algorithm": "scrypt" +} diff --git a/coins/takcoin.json b/coins/takcoin.json new file mode 100644 index 0000000..d84507c --- /dev/null +++ b/coins/takcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Takcoin", + "symbol": "TAK", + "algorithm": "sha256" +} diff --git a/coins/teacoin.json b/coins/teacoin.json new file mode 100644 index 0000000..c6a0826 --- /dev/null +++ b/coins/teacoin.json @@ -0,0 +1,5 @@ +{ + "name": "Teacoin", + "symbol": "TEA", + "algorithm": "sha256" +} diff --git a/coins/tekcoin.json b/coins/tekcoin.json new file mode 100644 index 0000000..44ba31b --- /dev/null +++ b/coins/tekcoin.json @@ -0,0 +1,5 @@ +{ + "name": "Tekcoin", + "symbol": "TEK", + "algorithm": "sha256" +} diff --git a/coins/tigercoin.json b/coins/tigercoin.json new file mode 100644 index 0000000..6edd7e3 --- /dev/null +++ b/coins/tigercoin.json @@ -0,0 +1,5 @@ +{ + "name": "Tigercoin", + "symbol": "TGC", + "algorithm": "sha256" +} diff --git a/config_example.json b/config_example.json index 869b3db..bc09c9b 100644 --- a/config_example.json +++ b/config_example.json @@ -1,6 +1,8 @@ { "logLevel": "debug", + "cliPort": 17117, + "clustering": { "enabled": true, "forks": "auto" @@ -26,47 +28,47 @@ "port": 6379 }, - "blockNotifyListener": { - "enabled": false, - "port": 8117, - "password": "test" - }, - - "coinSwitchListener": { - "enabled": false, - "host": "127.0.0.1", - "port": 8118, - "password": "test" - }, - - "proxy": { - "sha256": { + "switching": { + "switch1": { "enabled": false, - "port": "3333", - "diff": 10, - "varDiff": { - "minDiff": 16, - "maxDiff": 512, - "targetTime": 15, - "retargetTime": 90, - "variancePercent": 30 + "algorithm": "sha256", + "ports": { + "3333": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt": { + "switch2": { "enabled": false, - "port": "4444", - "diff": 10, - "varDiff": { - "minDiff": 16, - "maxDiff": 512, - "targetTime": 15, - "retargetTime": 90, - "variancePercent": 30 + "algorithm": "scrypt", + "ports": { + "4444": { + "diff": 10, + "varDiff": { + "minDiff": 16, + "maxDiff": 512, + "targetTime": 15, + "retargetTime": 90, + "variancePercent": 30 + } + } } }, - "scrypt-n": { + "switch3": { "enabled": false, - "port": "5555" + "algorithm": "x11", + "ports": { + "5555": { + "diff": 0.001 + } + } } }, diff --git a/init.js b/init.js index a7b15b6..bf5c0c9 100644 --- a/init.js +++ b/init.js @@ -5,8 +5,7 @@ var cluster = require('cluster'); var async = require('async'); var PoolLogger = require('./libs/logUtil.js'); -var BlocknotifyListener = require('./libs/blocknotifyListener.js'); -var CoinswitchListener = require('./libs/coinswitchListener.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'); @@ -23,6 +22,7 @@ if (!fs.existsSync('config.json')){ } var portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'}))); +var poolConfigs; var logger = new PoolLogger({ @@ -82,18 +82,68 @@ if (cluster.isWorker){ var buildPoolConfigs = function(){ var configs = {}; var configDir = 'pool_configs/'; + + var poolConfigFiles = []; + + + /* Get filenames of pool config json files that are enabled */ fs.readdirSync(configDir).forEach(function(file){ if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return; var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'}))); if (!poolOptions.enabled) return; - var coinFilePath = 'coins/' + poolOptions.coin; + poolOptions.fileName = file; + poolConfigFiles.push(poolOptions); + }); + + + /* Ensure no pool uses any of the same ports as another pool */ + for (var i = 0; i < poolConfigFiles.length; i++){ + var ports = Object.keys(poolConfigFiles[i].ports); + for (var f = 0; f < poolConfigFiles.length; f++){ + if (f === i) continue; + var portsF = Object.keys(poolConfigFiles[f].ports); + for (var g = 0; g < portsF.length; g++){ + if (ports.indexOf(portsF[g]) !== -1){ + logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName); + process.exit(1); + return; + } + } + + if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){ + logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].fileName + ' pool'); + process.exit(1); + return; + } + + } + } + + + poolConfigFiles.forEach(function(poolOptions){ + + poolOptions.coinFileName = poolOptions.coin; + + var coinFilePath = 'coins/' + poolOptions.coinFileName; if (!fs.existsSync(coinFilePath)){ - logger.error('Master', poolOptions.coin, 'could not find file: ' + coinFilePath); + logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath); return; } var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); poolOptions.coin = coinProfile; + + if (poolOptions.coin.name in configs){ + + logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName + + ' has same configured coin name ' + poolOptions.coin.name + ' as coins/' + + configs[poolOptions.coin.name].coinFileName + ' used by pool config ' + + configs[poolOptions.coin.name].fileName); + + process.exit(1); + return; + } + configs[poolOptions.coin.name] = poolOptions; if (!(coinProfile.algorithm in algos)){ @@ -107,7 +157,7 @@ var buildPoolConfigs = function(){ -var spawnPoolWorkers = function(portalConfig, poolConfigs){ +var spawnPoolWorkers = function(){ Object.keys(poolConfigs).forEach(function(coin){ var p = poolConfigs[coin]; @@ -130,6 +180,7 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ return; } + var serializedConfigs = JSON.stringify(poolConfigs); var numForks = (function(){ @@ -185,51 +236,111 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){ }; -var startBlockListener = function(portalConfig){ - //block notify options - //setup block notify here and use IPC to tell appropriate pools - var listener = new BlocknotifyListener(portalConfig.blockNotifyListener); +var startCliListener = function(){ + + var cliPort = portalConfig.cliPort; + + var listener = new CliListener(cliPort); listener.on('log', function(text){ - logger.debug('Master', 'Blocknotify', text); - }); - listener.on('hash', function(message){ + logger.debug('Master', 'CLI', text); + }).on('command', function(command, params, options, reply){ - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - - }); - listener.start(); + switch(command){ + case 'blocknotify': + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]}); + }); + reply('Pool workers notified'); + break; + case 'coinswitch': + processCoinSwitchCommand(params, options, reply); + break; + case 'reloadpool': + Object.keys(cluster.workers).forEach(function(id) { + cluster.workers[id].send({type: 'reloadpool', coin: params[0] }); + }); + reply('reloaded pool ' + params[0]); + break; + default: + reply('unrecognized command "' + command + '"'); + break; + } + }).start(); }; -// -// Receives authenticated events from coin switch listener and triggers proxy -// to swtich to a new coin. -// -var startCoinswitchListener = function(portalConfig){ - var listener = new CoinswitchListener(portalConfig.coinSwitchListener); - listener.on('log', function(text){ - logger.debug('Master', 'Coinswitch', text); - }); - listener.on('switchcoin', function(message){ - var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); - }); - var ipcMessage = { - type:'switch', - coin: message.coin - }; - Object.keys(cluster.workers).forEach(function(id) { - cluster.workers[id].send(ipcMessage); +var processCoinSwitchCommand = function(params, options, reply){ + + var logSystem = 'CLI'; + var logComponent = 'coinswitch'; + + var replyError = function(msg){ + reply(msg); + logger.error(logSystem, logComponent, msg); + }; + + if (!params[0]) { + replyError('Coin name required'); + return; + } + + if (!params[1] && !options.algorithm){ + replyError('If switch key is not provided then algorithm options must be specified'); + return; + } + else if (params[1] && !portalConfig.switching[params[1]]){ + replyError('Switch key not recognized: ' + params[1]); + return; + } + else if (options.algorithm && !Object.keys(portalConfig.switching).filter(function(s){ + return portalConfig.switching[s].algorithm === options.algorithm; + })[0]){ + replyError('No switching options contain the algorithm ' + options.algorithm); + return; + } + + var messageCoin = params[0].toLowerCase(); + var newCoin = Object.keys(poolConfigs).filter(function(p){ + return p.toLowerCase() === messageCoin; + })[0]; + + if (!newCoin){ + replyError('Switch message to coin that is not recognized: ' + messageCoin); + return; + } + + + var switchNames = []; + + if (params[1]) { + switchNames.push(params[1]); + } + else{ + for (var name in portalConfig.switching){ + if (portalConfig.switching[name].enabled && portalConfig.switching[name].algorithm === options.algorithm) + switchNames.push(name); + } + } + + switchNames.forEach(function(name){ + if (poolConfigs[newCoin].coin.algorithm !== portalConfig.switching[name].algorithm){ + replyError('Cannot switch a ' + + portalConfig.switching[name].algorithm + + ' algo pool to coin ' + newCoin + ' with ' + poolConfigs[newCoin].coin.algorithm + ' algo'); + return; + } + + Object.keys(cluster.workers).forEach(function (id) { + cluster.workers[id].send({type: 'coinswitch', coin: newCoin, switchName: name }); }); }); - listener.start(); + + reply('Switch message sent to pool workers'); + }; -var startRedisBlockListener = function(portalConfig){ + +var startRedisBlockListener = function(){ //block notify options //setup block notify here and use IPC to tell appropriate pools @@ -248,7 +359,7 @@ var startRedisBlockListener = function(portalConfig){ }; -var startPaymentProcessor = function(poolConfigs){ +var startPaymentProcessor = function(){ var enabledForAny = false; for (var pool in poolConfigs){ @@ -276,7 +387,7 @@ var startPaymentProcessor = function(poolConfigs){ }; -var startWebsite = function(portalConfig, poolConfigs){ +var startWebsite = function(){ if (!portalConfig.website.enabled) return; @@ -294,10 +405,10 @@ var startWebsite = function(portalConfig, poolConfigs){ }; -var startProfitSwitch = function(portalConfig, poolConfigs){ +var startProfitSwitch = function(){ - if (!portalConfig.profitSwitch.enabled){ - logger.error('Master', 'Profit', 'Profit auto switching disabled'); + if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){ + //logger.error('Master', 'Profit', 'Profit auto switching disabled'); return; } @@ -318,20 +429,18 @@ var startProfitSwitch = function(portalConfig, poolConfigs){ (function init(){ - var poolConfigs = buildPoolConfigs(); + poolConfigs = buildPoolConfigs(); - spawnPoolWorkers(portalConfig, poolConfigs); + spawnPoolWorkers(); - startPaymentProcessor(poolConfigs); + startPaymentProcessor(); - startBlockListener(portalConfig); + startRedisBlockListener(); - startCoinswitchListener(portalConfig); + startWebsite(); - startRedisBlockListener(portalConfig); + startProfitSwitch(); - startWebsite(portalConfig, poolConfigs); - - startProfitSwitch(portalConfig, poolConfigs); + startCliListener(); })(); diff --git a/libs/blocknotifyListener.js b/libs/blocknotifyListener.js deleted file mode 100644 index 4691c13..0000000 --- a/libs/blocknotifyListener.js +++ /dev/null @@ -1,69 +0,0 @@ -var events = require('events'); -var net = require('net'); - -var listener = module.exports = function listener(options){ - - var _this = this; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - if (!options || !options.enabled){ - emitLog('Blocknotify listener disabled'); - return; - } - - var blockNotifyServer = net.createServer(function(c) { - - emitLog('Block listener has incoming connection'); - var data = ''; - try { - c.on('data', function (d) { - emitLog('Block listener received blocknotify data'); - data += d; - if (data.slice(-1) === '\n') { - c.end(); - } - }); - c.on('end', function () { - - emitLog('Block listener connection ended'); - - var message; - - try{ - message = JSON.parse(data); - } - catch(e){ - emitLog('Block listener failed to parse message ' + data); - return; - } - - if (message.password === options.password) { - _this.emit('hash', message); - } - else - emitLog('Block listener received notification with incorrect password'); - - }); - } - catch(e){ - emitLog('Block listener had an error: ' + e); - } - - }); - blockNotifyServer.listen(options.port, function() { - emitLog('Block notify listener server started on port ' + options.port) - }); - - emitLog("Block listener is enabled, starting server on port " + options.port); - } - - - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/cliListener.js b/libs/cliListener.js new file mode 100644 index 0000000..efb18cf --- /dev/null +++ b/libs/cliListener.js @@ -0,0 +1,42 @@ +var events = require('events'); +var net = require('net'); + +var listener = module.exports = function listener(port){ + + var _this = this; + + var emitLog = function(text){ + _this.emit('log', text); + }; + + + this.start = function(){ + net.createServer(function(c) { + + var data = ''; + try { + c.on('data', function (d) { + data += d; + if (data.slice(-1) === '\n') { + var message = JSON.parse(data); + _this.emit('command', message.command, message.params, message.options, function(message){ + c.end(message); + }); + } + }); + c.on('end', function () { + + }); + } + catch(e){ + emitLog('CLI listener failed to parse message ' + data); + } + + }).listen(port, '127.0.0.1', function() { + emitLog('CLI listening on port ' + port) + }); + } + +}; + +listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/coinswitchListener.js b/libs/coinswitchListener.js deleted file mode 100644 index fad0d2f..0000000 --- a/libs/coinswitchListener.js +++ /dev/null @@ -1,56 +0,0 @@ -var events = require('events'); -var net = require('net'); - -var listener = module.exports = function listener(options){ - - var _this = this; - - var emitLog = function(text){ - _this.emit('log', text); - }; - - - this.start = function(){ - if (!options || !options.enabled){ - emitLog('Coinswitch listener disabled'); - return; - } - - var coinswitchServer = net.createServer(function(c) { - - emitLog('Coinswitch listener has incoming connection'); - var data = ''; - try { - c.on('data', function (d) { - emitLog('Coinswitch listener received switch request'); - data += d; - if (data.slice(-1) === '\n') { - c.end(); - } - }); - c.on('end', function () { - - var message = JSON.parse(data); - if (message.password === options.password) { - _this.emit('switchcoin', message); - } - else - emitLog('Coinswitch listener received notification with incorrect password'); - - }); - } - catch(e){ - emitLog('Coinswitch listener failed to parse message ' + data); - } - - }); - coinswitchServer.listen(options.port, function() { - emitLog('Coinswitch notify listener server started on port ' + options.port) - }); - - emitLog("Coinswitch listener is enabled, starting server on port " + options.port); - } - -}; - -listener.prototype.__proto__ = events.EventEmitter.prototype; diff --git a/libs/mposCompatibility.js b/libs/mposCompatibility.js index 659d290..0a3b1c7 100644 --- a/libs/mposCompatibility.js +++ b/libs/mposCompatibility.js @@ -7,38 +7,26 @@ module.exports = function(logger, poolConfig){ var connection; + var logIdentify = 'MySQL'; var logComponent = coin; function connect(){ - connection = mysql.createConnection({ + + connection = mysql.createPool({ host: mposConfig.host, port: mposConfig.port, user: mposConfig.user, password: mposConfig.password, database: mposConfig.database }); - connection.connect(function(err){ - if (err) - logger.error(logIdentify, logComponent, 'Could not connect to mysql database: ' + JSON.stringify(err)) - else{ - logger.debug(logIdentify, logComponent, 'Successful connection to MySQL database'); - } - }); - connection.on('error', function(err){ - if(err.code === 'PROTOCOL_CONNECTION_LOST') { - logger.warning(logIdentify, logComponent, 'Lost connection to MySQL database, attempting reconnection...'); - connect(); - } - else{ - logger.error(logIdentify, logComponent, 'Database error: ' + JSON.stringify(err)) - } - }); + + } connect(); this.handleAuth = function(workerName, password, authCallback){ - + connection.query( 'SELECT password FROM pool_worker WHERE username = LOWER(?)', [workerName.toLowerCase()], @@ -48,8 +36,39 @@ module.exports = function(logger, poolConfig){ JSON.stringify(err)); authCallback(false); } - else if (!result[0]) - authCallback(false); + else if (!result[0]){ + if(mposConfig.autoCreateWorker){ + var account = workerName.split('.')[0]; + connection.query( + 'SELECT id,username FROM accounts WHERE username = LOWER(?)', + [account.toLowerCase()], + function(err, result){ + if (err){ + logger.error(logIdentify, logComponent, 'Database error when authenticating account: ' + + JSON.stringify(err)); + authCallback(false); + }else if(!result[0]){ + authCallback(false); + }else{ + connection.query( + "INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);", + [result[0].id,workerName.toLowerCase(),password], + function(err, result){ + if (err){ + logger.error(logIdentify, logComponent, 'Database error when insert worker: ' + + JSON.stringify(err)); + authCallback(false); + }else { + authCallback(true); + } + }) + } + } + ); + }else{ + authCallback(false); + } + } else if (mposConfig.stratumAuth === 'worker') authCallback(true); else if (result[0].password === password) diff --git a/libs/paymentProcessor.js b/libs/paymentProcessor.js index 3d3482a..638c64d 100644 --- a/libs/paymentProcessor.js +++ b/libs/paymentProcessor.js @@ -2,7 +2,7 @@ var redis = require('redis'); var async = require('async'); var Stratum = require('stratum-pool'); - +var util = require('stratum-pool/lib/util.js'); module.exports = function(logger){ @@ -38,8 +38,6 @@ module.exports = function(logger){ }); }); - - }; @@ -216,6 +214,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ return ['gettransaction', [r.txHash]]; }); + batchRPCcommand.push(['getaccount', [poolOptions.address]]); + daemon.batchCmd(batchRPCcommand, function(error, txDetails){ if (error || !txDetails){ @@ -224,7 +224,15 @@ function SetupForPool(logger, poolOptions, setupFinished){ return; } + var addressAccount; + txDetails.forEach(function(tx, i){ + + if (i === txDetails.length - 1){ + addressAccount = tx.result; + return; + } + var round = rounds[i]; if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){ @@ -273,7 +281,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ 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 = r.reward / r.amount; + var roundMagnitude = Math.round(r.reward / r.amount); if (!magnitude) { magnitude = roundMagnitude; @@ -303,7 +311,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ callback('Check finished - no confirmed or orphaned blocks found'); } else{ - callback(null, rounds, magnitude); + callback(null, rounds, magnitude, addressAccount); } }); }, @@ -311,7 +319,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ /* 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, callback){ + function(rounds, magnitude, addressAccount, callback){ var shareLookups = rounds.map(function(r){ @@ -356,11 +364,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ var reward = round.reward * (1 - processingConfig.feePercent); var totalShares = Object.keys(workerShares).reduce(function(p, c){ - return p + parseInt(workerShares[c]) + return p + parseFloat(workerShares[c]) }, 0); for (var worker in workerShares){ - var percent = parseInt(workerShares[worker]) / totalShares; + var percent = parseFloat(workerShares[worker]) / totalShares; var workerRewardTotal = Math.floor(reward * percent); if (!(worker in workerRewards)) workerRewards[worker] = 0; workerRewards[worker] += workerRewardTotal; @@ -370,13 +378,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands); + callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount); }); }, /* Does a batch call to redis to get worker existing balances from coin_balances*/ - function(rounds, magnitude, workerRewards, orphanMergeCommands, callback){ + function(rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount, callback){ var workers = Object.keys(workerRewards); @@ -394,7 +402,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } - callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances); + callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount); }); }, @@ -406,12 +414,11 @@ function SetupForPool(logger, poolOptions, setupFinished){ 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, callback){ + function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount, callback){ //number of satoshis in a single coin unit - this can be different for coins so we calculate it :) - - daemon.cmd('getbalance', [''], function(results){ + daemon.cmd('getbalance', [addressAccount || ''], function(results){ var totalBalance = results[0].response * magnitude; var toBePaid = 0; @@ -474,9 +481,9 @@ function SetupForPool(logger, poolOptions, setupFinished){ /* 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 + ' and collect ' + feeAmountToBeCollected + ' as fees' + - ' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver + - ', needs to be at least ' + minReserveSatoshis); + (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; } @@ -495,7 +502,8 @@ function SetupForPool(logger, poolOptions, setupFinished){ } })(); movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]); - roundsToDelete.push(coin + '_shares:round' + r.height) + if (r.category === 'generate') + roundsToDelete.push(coin + '_shares:round' + r.height) }); var finalRedisCommands = []; @@ -522,23 +530,23 @@ function SetupForPool(logger, poolOptions, setupFinished){ finalRedisCommands.push(['bgsave']); - callback(null, magnitude, workerPayments, finalRedisCommands); + callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); }); }, - function(magnitude, workerPayments, finalRedisCommands, callback) { + 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); + callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount); }); }, - function(magnitude, workerPayments, finalRedisCommands, callback){ + function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback){ //This does the final all-or-nothing atom transaction if block deamon sent payments var finalizeRedisTx = function(){ @@ -563,14 +571,19 @@ function SetupForPool(logger, poolOptions, setupFinished){ var totalAmountUnits = 0; for (var address in workerPayments){ var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision); - addressAmounts[address] = coinUnits; + var properAddress = getProperAddress(address); + if (!properAddress){ + logger.error(logSystem, logComponent, 'Could not convert pubkey ' + address + ' into address'); + continue; + } + addressAmounts[properAddress] = coinUnits; totalAmountUnits += coinUnits; } logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); processingPayments = true; - daemon.cmd('sendmany', ['', addressAmounts], function(results){ + daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){ if (results[0].error){ callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error)); @@ -591,7 +604,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ } var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision)); var poolFees = feeAmountUnits - results[0].response.fee; - daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){ + 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; @@ -619,6 +632,13 @@ function SetupForPool(logger, poolOptions, setupFinished){ }; + var getProperAddress = function(address){ + if (address.length === 40){ + return util.addressFromEx(poolOptions.address, address); + } + else return address; + }; + var withdrawalProfit = function(){ if (!processingConfig.feeWithdrawalThreshold) return; @@ -641,7 +661,7 @@ function SetupForPool(logger, poolOptions, setupFinished){ daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){ if (results[0].error){ - logger.debug(logSystem, logComponent, 'Profit withdrawal finished - error with sendmany ' + logger.debug(logSystem, logComponent, 'Profit withdrawal of ' + withdrawalAmount + ' failed - error with sendmany ' + JSON.stringify(results[0].error)); return; } @@ -652,5 +672,4 @@ function SetupForPool(logger, poolOptions, setupFinished){ }); }; - -}; \ No newline at end of file +} \ No newline at end of file diff --git a/libs/poolWorker.js b/libs/poolWorker.js index 4c771c8..fc310b7 100644 --- a/libs/poolWorker.js +++ b/libs/poolWorker.js @@ -18,6 +18,8 @@ module.exports = function(logger){ var proxySwitch = {}; + var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); + //Handle messages from master process sent via IPC process.on('message', function(message) { switch(message.type){ @@ -42,26 +44,21 @@ module.exports = function(logger){ break; // IPC message for pool switching - case 'switch': + case 'coinswitch': var logSystem = 'Proxy'; var logComponent = 'Switch'; var logSubCat = 'Thread ' + (parseInt(forkId) + 1); - var messageCoin = message.coin.toLowerCase(); - var newCoin = Object.keys(pools).filter(function(p){ - return p.toLowerCase() === messageCoin; - })[0]; + var switchName = message.switchName; - if (!newCoin){ - logger.debug(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin); - break; - } + var newCoin = message.coin; var algo = poolConfigs[newCoin].coin.algorithm; + var newPool = pools[newCoin]; - var oldCoin = proxySwitch[algo].currentPool; + var oldCoin = proxySwitch[switchName].currentPool; var oldPool = pools[oldCoin]; - var proxyPort = proxySwitch[algo].port; + var proxyPorts = Object.keys(proxySwitch[switchName].ports); if (newCoin == oldCoin) { logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin); @@ -74,25 +71,23 @@ module.exports = function(logger){ oldPool.relinquishMiners( function (miner, cback) { // relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there. - cback(miner.client.socket.localPort == proxyPort) + cback(proxyPorts.indexOf(miner.client.socket.localPort.toString()) !== -1) }, function (clients) { newPool.attachMiners(clients); } ); - proxySwitch[algo].currentPool = newCoin; + proxySwitch[switchName].currentPool = newCoin; - var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) - redisClient.on('ready', function(){ - redisClient.hset('proxyState', algo, newCoin, function(error, obj) { - if (error) { - logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err)) - } - else { - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); - } - }); + redisClient.hset('proxyState', algo, newCoin, function(error, obj) { + if (error) { + logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err)) + } + else { + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); + } }); + } break; } @@ -117,9 +112,9 @@ module.exports = function(logger){ //Functions required for MPOS compatibility if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ - var mposCompat = new MposCompatibility(logger, poolOptions) + var mposCompat = new MposCompatibility(logger, poolOptions); - handlers.auth = function(workerName, password, authCallback){ + handlers.auth = function(port, workerName, password, authCallback){ mposCompat.handleAuth(workerName, password, authCallback); }; @@ -135,12 +130,32 @@ module.exports = function(logger){ //Functions required for internal payment processing else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){ - var shareProcessor = new ShareProcessor(logger, poolOptions) + var shareProcessor = new ShareProcessor(logger, poolOptions); - handlers.auth = function(workerName, password, authCallback){ + handlers.auth = function(port, workerName, password, authCallback){ if (shareProcessing.internal.validateWorkerAddress !== 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; + } + } + } + pool.daemon.cmd('validateaddress', [workerName], function(results){ var isValid = results.filter(function(r){return r.response.isvalid}).length > 0; authCallback(isValid); @@ -153,8 +168,8 @@ module.exports = function(logger){ }; } - var authorizeFN = function (ip, workerName, password, callback) { - handlers.auth(workerName, password, function(authorized){ + var authorizeFN = function (ip, port, workerName, password, callback) { + handlers.auth(port, workerName, password, function(authorized){ var authString = authorized ? 'Authorized' : 'Unauthorized '; @@ -202,9 +217,9 @@ module.exports = function(logger){ }); - if (typeof(portalConfig.proxy) !== 'undefined') { + if (portalConfig.switching) { - var logSystem = 'Proxy'; + var logSystem = 'Switching'; var logComponent = 'Setup'; var logSubCat = 'Thread ' + (parseInt(forkId) + 1); @@ -215,73 +230,93 @@ 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(portalConfig.redis.port, portalConfig.redis.host) - redisClient.on('ready', function(){ - redisClient.hgetall("proxyState", function(error, obj) { - if (error || obj == null) { - logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); - } - else { - proxyState = obj; - logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); - } - // - // Setup proxySwitch object to control proxy operations from configuration and any restored - // state. Each algorithm has a listening port, current coin name, and an active pool to - // which traffic is directed when activated in the config. - // - // In addition, the proxy config also takes diff and varDiff parmeters the override the - // defaults for the standard config of the coin. - // - Object.keys(portalConfig.proxy).forEach(function(algorithm) { - if (portalConfig.proxy[algorithm].enabled === true) { - var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); - proxySwitch[algorithm] = { - port: portalConfig.proxy[algorithm].port, - currentPool: initalPool, - proxy: {} - }; - - // 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; - } - Object.keys(pools).forEach(function (coinName) { - var a = poolConfigs[coinName].coin.algorithm; - var p = pools[coinName]; - if (a === algorithm) { - p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff); + /*redisClient.on('error', function(err){ + logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err); + });*/ + + redisClient.hgetall("proxyState", function(error, obj) { + if (error || obj == null) { + //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis'); + } + else { + proxyState = obj; + logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); + } + + // + // Setup proxySwitch object to control proxy operations from configuration and any restored + // state. Each algorithm has a listening port, current coin name, and an active pool to + // which traffic is directed when activated in the config. + // + // In addition, the proxy config also takes diff and varDiff parmeters the override the + // defaults for the standard config of the coin. + // + Object.keys(portalConfig.switching).forEach(function(switchName) { + + 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: [] + }; + + + // 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; + }*/ + + + + 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); } - }); + } + }); - proxySwitch[algorithm].proxy = net.createServer(function(socket) { - var currentPool = proxySwitch[algorithm].currentPool; - var logSubCat = 'Thread ' + (parseInt(forkId) + 1); - logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port - + ' routing to ' + currentPool); + 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(proxySwitch[algorithm].port), function() { - logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port - + ' into ' + proxySwitch[algorithm].currentPool); + }).listen(parseInt(port), function() { + logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName + + '" listening for ' + algorithm + + ' on port ' + port + + ' into ' + proxySwitch[switchName].currentPool); }); - } - else { - logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); - } - }); + proxySwitch[switchName].servers.push(f); + }); + + + } + else { + //logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.'); + } }); - }).on('error', function(err){ - logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err); }); } diff --git a/libs/profitSwitch.js b/libs/profitSwitch.js index 7c1f449..6e0e6c6 100644 --- a/libs/profitSwitch.js +++ b/libs/profitSwitch.js @@ -420,31 +420,28 @@ module.exports = function(logger){ }; this.getDaemonInfoForCoin = function(symbol, cfg, callback){ var daemon = new Stratum.daemon.interface([cfg]); - daemon.once('online', function(){ - daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ - if (result[0].error != null){ - logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0])); - callback(null); // fail gracefully for each coin - return; - } - var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - var response = result[0].response; - - // some shitcoins dont provide target, only bits, so we need to deal with both - var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); - coinStatus.difficulty = parseFloat((diff1.toNumber() / target.toNumber()).toFixed(9)); - logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); - - coinStatus.reward = new Number(response.coinbasevalue / 100000000); - callback(null); - }); - }).once('connectionFailed', function(error){ - logger.error(logSystem, symbol, JSON.stringify(error)); - callback(null); // fail gracefully for each coin - }).on('error', function(error){ + daemon.on('error', function(error){ logger.error(logSystem, symbol, JSON.stringify(error)); callback(null); // fail gracefully for each coin }).init(); + + daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) { + if (result[0].error != null) { + logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0])); + callback(null); // fail gracefully for each coin + return; + } + var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; + var response = result[0].response; + + // some shitcoins dont provide target, only bits, so we need to deal with both + var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits); + coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9)); + logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty); + + coinStatus.reward = response.coinbasevalue / 100000000; + callback(null); + }); }; @@ -453,8 +450,8 @@ module.exports = function(logger){ Object.keys(profitStatus).forEach(function(algo){ Object.keys(profitStatus[algo]).forEach(function(symbol){ var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; - coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); - coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); + coinStatus.blocksPerMhPerHour = 86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000)); + coinStatus.coinsPerMhPerHour = coinStatus.reward * coinStatus.blocksPerMhPerHour; }); }); callback(null); @@ -467,7 +464,7 @@ module.exports = function(logger){ var bestExchange; var bestCoin; - var bestBtcPerMhPerHour = new Number(0); + var bestBtcPerMhPerHour = 0; Object.keys(profitStatus[algo]).forEach(function(symbol) { var coinStatus = profitStatus[algo][symbol]; @@ -475,7 +472,7 @@ module.exports = function(logger){ Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ var exchangeData = coinStatus.exchangeInfo[exchange]; if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour); + var btcPerMhPerHour = exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour; if (btcPerMhPerHour > bestBtcPerMhPerHour){ bestBtcPerMhPerHour = btcPerMhPerHour; bestExchange = exchange; @@ -485,7 +482,7 @@ module.exports = function(logger){ logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); } if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){ - var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc); + var btcPerMhPerHour = (exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc; if (btcPerMhPerHour > bestBtcPerMhPerHour){ bestBtcPerMhPerHour = btcPerMhPerHour; bestExchange = exchange; @@ -497,14 +494,21 @@ module.exports = function(logger){ }); }); logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s'); - if (portalConfig.coinSwitchListener.enabled){ - var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () { - client.write(JSON.stringify({ - password: portalConfig.coinSwitchListener.password, - coin: bestCoin - }) + '\n'); - }); - } + + + var client = net.connect(portalConfig.cliPort, function () { + client.write(JSON.stringify({ + command: 'coinswitch', + params: [bestCoin], + options: {algorithm: algo} + }) + '\n'); + }).on('error', function(error){ + if (error.code === 'ECONNREFUSED') + logger.error(logSystem, 'CLI', 'Could not connect to NOMP instance on port ' + portalConfig.cliPort); + else + logger.error(logSystem, 'CLI', 'Socket error ' + JSON.stringify(error)); + }); + }); }; diff --git a/libs/stats.js b/libs/stats.js index 0893506..527e19d 100644 --- a/libs/stats.js +++ b/libs/stats.js @@ -148,7 +148,11 @@ module.exports = function(logger, portalConfig, poolConfigs){ symbol: poolConfigs[coinName].coin.symbol.toUpperCase(), algorithm: poolConfigs[coinName].coin.algorithm, hashrates: replies[i + 1], - poolStats: replies[i + 2] != null ? replies[i + 2] : { validShares: 0, validBlocks: 0, invalidShares: 0 }, + 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 + }, blocks: { pending: replies[i + 3], confirmed: replies[i + 4], @@ -191,9 +195,10 @@ module.exports = function(logger, portalConfig, poolConfigs){ else coinStats.workers[worker] = workerShares; }); - var shareMultiplier = algos[coinStats.algorithm].multiplier || 0; - var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; - coinStats.hashrate = hashratePre | 0; + + var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; + coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; + coinStats.workerCount = Object.keys(coinStats.workers).length; portalStats.global.workers += coinStats.workerCount; diff --git a/libs/website.js b/libs/website.js index 44d9b55..3af59c5 100644 --- a/libs/website.js +++ b/libs/website.js @@ -3,18 +3,24 @@ var fs = require('fs'); var path = require('path'); var async = require('async'); +var watch = require('node-watch'); +var redis = require('redis'); + var dot = require('dot'); var express = require('express'); var bodyParser = require('body-parser'); var compress = require('compression'); -var watch = require('node-watch'); +var Stratum = require('stratum-pool'); +var util = require('stratum-pool/lib/util.js'); var api = require('./api.js'); module.exports = function(logger){ + dot.templateSettings.strip = false; + var portalConfig = JSON.parse(process.env.portalConfig); var poolConfigs = JSON.parse(process.env.pools); @@ -31,8 +37,10 @@ module.exports = function(logger){ 'home.html': '', 'getting_started.html': 'getting_started', 'stats.html': 'stats', + 'tbs.html': 'tbs', 'api.html': 'api', - 'admin.html': 'admin' + 'admin.html': 'admin', + 'mining_key.html': 'mining_key' }; var pageTemplates = {}; @@ -40,6 +48,9 @@ module.exports = function(logger){ var pageProcessed = {}; var indexesProcessed = {}; + var keyScriptTemplate = ''; + var keyScriptProcessed = ''; + var processTemplates = function(){ @@ -112,6 +123,87 @@ module.exports = function(logger){ setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000); + var buildKeyScriptPage = function(){ + async.waterfall([ + function(callback){ + var client = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); + client.hgetall('coinVersionBytes', function(err, coinBytes){ + if (err){ + client.quit(); + return callback('Failed grabbing coin version bytes from redis ' + JSON.stringify(err)); + } + callback(null, client, coinBytes || {}); + }); + }, + function (client, coinBytes, callback){ + var enabledCoins = Object.keys(poolConfigs).map(function(c){return c.toLowerCase()}); + var missingCoins = []; + enabledCoins.forEach(function(c){ + if (!(c in coinBytes)) + missingCoins.push(c); + }); + callback(null, client, coinBytes, missingCoins); + }, + function(client, coinBytes, missingCoins, callback){ + var coinsForRedis = {}; + async.each(missingCoins, function(c, cback){ + var coinInfo = (function(){ + for (var pName in poolConfigs){ + if (pName.toLowerCase() === c) + return { + daemon: poolConfigs[pName].shareProcessing.internal.daemon, + address: poolConfigs[pName].address + } + } + })(); + var daemon = new Stratum.daemon.interface([coinInfo.daemon]); + 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)); + cback(); + return; + } + + var vBytePub = util.getVersionByte(coinInfo.address)[0]; + var vBytePriv = util.getVersionByte(result[0].response)[0]; + + coinBytes[c] = vBytePub.toString() + ',' + vBytePriv.toString(); + coinsForRedis[c] = coinBytes[c]; + cback(); + }); + }, function(err){ + callback(null, client, coinBytes, coinsForRedis); + }); + }, + function(client, coinBytes, coinsForRedis, callback){ + if (Object.keys(coinsForRedis).length > 0){ + client.hmset('coinVersionBytes', coinsForRedis, function(err){ + if (err) + logger.error(logSystem, 'Init', 'Failed inserting coin byte version into redis ' + JSON.stringify(err)); + client.quit(); + }); + } + else{ + client.quit(); + } + callback(null, coinBytes); + } + ], function(err, coinBytes){ + if (err){ + logger.error(logSystem, 'Init', err); + return; + } + try{ + keyScriptTemplate = dot.template(fs.readFileSync('website/key.html', {encoding: 'utf8'})); + keyScriptProcessed = keyScriptTemplate({coins: coinBytes}); + } + catch(e){ + logger.error(logSystem, 'Init', 'Failed to read key.html file'); + } + }); + + }; + buildKeyScriptPage(); var getPage = function(pageId){ if (pageId in pageProcessed){ @@ -146,6 +238,10 @@ module.exports = function(logger){ next(); }); + app.get('/key.html', function(reg, res, next){ + res.end(keyScriptProcessed); + }); + app.get('/:page', route); app.get('/', route); @@ -181,4 +277,4 @@ module.exports = function(logger){ }); -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index dc3e7c1..133d279 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-open-mining-portal", - "version": "0.0.3", + "version": "0.0.4", "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool", "keywords": [ "stratum", diff --git a/scripts/blockNotify.js b/scripts/blockNotify.js deleted file mode 100644 index 42a0f8c..0000000 --- a/scripts/blockNotify.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - This script should be hooked to the coin daemon as follow: - 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 - */ - -var net = require('net'); -var config = process.argv[2]; -var parts = config.split(':'); -var host = parts[0]; -var port = parts[1]; -var password = process.argv[3]; -var coin = process.argv[4]; -var blockHash = process.argv[5]; - -var client = net.connect(port, host, function () { - console.log('client connected'); - client.write(JSON.stringify({ - password: password, - coin: coin, - hash: blockHash - }) + '\n'); -}); - -client.on('data', function (data) { - console.log(data.toString()); - //client.end(); -}); - -client.on('end', function () { - console.log('client disconnected'); - //process.exit(); -}); \ No newline at end of file diff --git a/scripts/blocknotify.c b/scripts/blocknotify.c index 78c9db7..a119096 100644 --- a/scripts/blocknotify.c +++ b/scripts/blocknotify.c @@ -16,76 +16,69 @@ Simple lightweight & fast - a more efficient block notify script in pure C. (may also work as coin switch) -Platforms : Linux,BSD,Solaris (mostly OS independent) +Platforms : Linux, BSD, Solaris (mostly OS independent) Build with: gcc blocknotify.c -o blocknotify -Usage in daemon coin.conf - blocknotify="/bin/blocknotify 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" +Example usage in daemon coin.conf using default NOMP CLI port of 17117 + blocknotify="/bin/blocknotify 127.0.0.1:17117 dogecoin %s" -*NOTE* If you use "localhost" as hostname you may get a "13" error (socket / connect / send may consider "localhost" as a broadcast address) -// {"password":"notepas","coin":"Xcoin","hash":"d2191a8b644c9cd903439edf1d89ee060e196b3e116e0d48a3f11e5e3987a03b"} -// simplest connect + send json string to server -# $Id: blocknotify.c,v 0.1 2014/04/07 22:38:09 sysman Exp $ */ int main(int argc, char **argv) { - int sockfd,n; - struct sockaddr_in servaddr,cliaddr; - char sendline[1000]; - char recvline[1000]; - char host[200]; - char *p,*arg,*errptr; - int port; + int sockfd,n; + struct sockaddr_in servaddr, cliaddr; + char sendline[1000]; + char recvline[1000]; + char host[200]; + char *p, *arg, *errptr; + int port; - if (argc < 4) - { - // print help - printf("NOMP pool block notify\n usage: \n"); - exit(1); - } + if (argc < 3) + { + // print help + printf("NOMP pool block notify\n usage: \n"); + exit(1); + } - strncpy(host,argv[1],(sizeof(host)-1)); - p=host; + strncpy(host, argv[1], (sizeof(host)-1)); + p = host; - if ( (arg=strchr(p,':')) ) - { - *arg='\0'; + if ( (arg = strchr(p,':')) ) + { + *arg = '\0'; - errno=0; // reset errno - port=strtol(++arg,&errptr,10); + errno = 0; // reset errno + port = strtol(++arg, &errptr, 10); - if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); } - // if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now - // printf("host %s:%d\n",host,port); - } + if ( (errno != 0) || (errptr == arg) ) + { + fprintf(stderr, "port number fail [%s]\n", errptr); + } - // printf("pass: %s coin: %s block:[%s]\n",argv[2],argv[3],argv[4]); - snprintf(sendline,sizeof(sendline)-1, - "{\"password\":\"%s\",\"coin\":\"%s\",\"hash\":\"%s\"}\n", - argv[2], argv[3], argv[4]); + } - // printf("sendline:[%s]",sendline); + snprintf(sendline, sizeof(sendline) - 1, "{\"command\":\"blocknotify\",\"params\":[\"%s\",\"%s\"]}\n", argv[2], argv[3]); - sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); - bzero(&servaddr,sizeof(servaddr)); - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr=inet_addr(host); - servaddr.sin_port=htons(port); - connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); + sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + bzero(&servaddr, sizeof(servaddr)); + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = inet_addr(host); + servaddr.sin_port = htons(port); + connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); - int result = send(sockfd,sendline,strlen(sendline),0); - close(sockfd); + int result = send(sockfd, sendline, strlen(sendline), 0); + close(sockfd); - if(result == -1) { - printf("Error sending: %i\n",errno); + if(result == -1) { + printf("Error sending: %i\n", errno); exit(-1); - } - exit(0); + } + exit(0); } diff --git a/scripts/cli.js b/scripts/cli.js new file mode 100644 index 0000000..5e1cfd6 --- /dev/null +++ b/scripts/cli.js @@ -0,0 +1,37 @@ +var net = require('net'); + +var defaultPort = 17117; +var defaultHost = '127.0.0.1'; + +var args = process.argv.slice(2); +var params = []; +var options = {}; + +for(var i = 0; i < args.length; i++){ + if (args[i].indexOf('-') === 0 && args[i].indexOf('=') !== -1){ + var s = args[i].substr(1).split('='); + options[s[0]] = s[1]; + } + else + params.push(args[i]); +} + +var command = params.shift(); + + + +var client = net.connect(options.port || defaultPort, options.host || defaultHost, function () { + client.write(JSON.stringify({ + command: command, + params: params, + options: options + }) + '\n'); +}).on('error', function(error){ + if (error.code === 'ECONNREFUSED') + console.log('Could not connect to NOMP instance at ' + defaultHost + ':' + defaultPort); + else + console.log('Socket error ' + JSON.stringify(error)); +}).on('data', function(data) { + console.log(data.toString()); +}).on('close', function () { +}); \ No newline at end of file diff --git a/scripts/coinSwitch.js b/scripts/coinSwitch.js deleted file mode 100644 index acf6ac1..0000000 --- a/scripts/coinSwitch.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - This script demonstrates sending a coin switch request and can be invoked from the command line - with: - - "node coinSwitch.js 127.0.0.1:8118 password %s" - - where <%s> is the name of the coin proxy miners will be switched onto. - - If the coin name is not configured, disabled or matches the existing proxy setting, no action - will be taken by NOMP on receipt of the message. - */ - -var net = require('net'); -var config = process.argv[2]; -var parts = config.split(':'); -var host = parts[0]; -var port = parts[1]; -var password = process.argv[3]; -var coin = process.argv[4]; - -var client = net.connect(port, host, function () { - console.log('client connected'); - client.write(JSON.stringify({ - password: password, - coin: coin - }) + '\n'); -}); - -client.on('data', function (data) { - console.log(data.toString()); - //client.end(); -}); - -client.on('end', function () { - console.log('client disconnected'); - //process.exit(); -}); diff --git a/website/index.html b/website/index.html index 0542569..03544ac 100644 --- a/website/index.html +++ b/website/index.html @@ -42,7 +42,13 @@
  •   - Stats + Graph Stats + +
  • +
  • + +   + Tab Stats
  • diff --git a/website/key.html b/website/key.html new file mode 100644 index 0000000..9978e9e --- /dev/null +++ b/website/key.html @@ -0,0 +1,2798 @@ + + + + + + Mining Key Script + + + + + + + + +
    +

    Mining key generation or input options:

    + +
    +
    1)
    +
    Create new private key
    + +
    +
    - or-
    +
    +
    2)
    +
    Import existing private key
    + +
    +
    - or-
    +
    +
    3)
    +
    Input private key hex
    + + +
    Private key must be 64 hexadecimal characters
    +
    +
    + +
    + + + +
    +
    NO NOT LOSE THIS PRIVATE KEY. Any coins mined using this public key can + only be controlled with this private key.
    + +
    Private key:
    +
    Key for mining (hashed public key):
    + +

    + An address for any type of coin can be derived from this mining key - and each of those coin address + can only be controlled by this private key. +

    + +
    + +
    +
    Backup your private key
    + + + + + + + + + +
    Step 1) + +
    Step 2) + +
    +
    + +
    +
    Coin formatted keys
    + + +
    Public address
    + + +
    Private key in wallet import format
    + + +
    +
    How to import your private key for :
    +
      +
    1. Open your wallet app
    2. +
    3. Go to Help -> click Debug window -> click Console tab
    4. +
    5. Enter the following command: importprivkey
    6. +
    +
    + +
    + + +
    + + + + + + + + + \ No newline at end of file diff --git a/website/pages/api.html b/website/pages/api.html index 6eaea98..b0f73c6 100644 --- a/website/pages/api.html +++ b/website/pages/api.html @@ -1,3 +1,10 @@
    API Docs here -
    \ No newline at end of file + +
      +
    • /stats - raw json statistic
    • +
    • /pool_stats - historical time per pool json
    • +
    • /live_stats - live stats
    • + +
    + diff --git a/website/pages/mining_key.html b/website/pages/mining_key.html new file mode 100644 index 0000000..d380676 --- /dev/null +++ b/website/pages/mining_key.html @@ -0,0 +1,25 @@ + + +
    + +

    + This script run client-side (in your browser). For maximum security download the script and run it locally and + offline in a modern web browser. +

    + + + +
    diff --git a/website/pages/tbs.html b/website/pages/tbs.html new file mode 100644 index 0000000..9ca8352 --- /dev/null +++ b/website/pages/tbs.html @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + {{ for(var pool in it.stats.pools) { }} + + + + + + + + + + + + + {{ } }} +
    PoolAlgoWorkersValid SharesInvalid SharesTotal BlocksPendingConfirmedOrphanedHashrate
    {{=it.stats.pools[pool].name}}{{=it.stats.pools[pool].algorithm}}{{=Object.keys(it.stats.pools[pool].workers).length}}{{=it.stats.pools[pool].poolStats.validShares}}{{=it.stats.pools[pool].poolStats.invalidShares}}{{=it.stats.pools[pool].poolStats.validBlocks}}{{=it.stats.pools[pool].blocks.pending}}{{=it.stats.pools[pool].blocks.confirmed}}{{=it.stats.pools[pool].blocks.orphaned}}{{=it.stats.pools[pool].hashrateString}}