Updated coin configs

This commit is contained in:
Jerry Brady 2014-05-01 23:25:41 +00:00
commit 12946fdcf0
48 changed files with 3780 additions and 587 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
node_modules/ node_modules/
.idea/ .idea/
config.json config.json
pool_configs/*.json
!pool_configs/litecoin_example.json

140
README.md
View File

@ -7,7 +7,7 @@ responsive user-friendly front-end website featuring mining instructions, in-dep
#### Table of Contents #### Table of Contents
* [Features](#features) * [Features](#features)
* [Attack Mitigation](##attack-mitigation) * [Attack Mitigation](#attack-mitigation)
* [Security](#security) * [Security](#security)
* [Planned Features](#planned-features) * [Planned Features](#planned-features)
* [Community Support](#community--support) * [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) * 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. 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 * 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 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. 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 * Coin-switching ports using coin-networks and crypto-exchange APIs to detect profitability. Miner's connect to these ports
of this software. To use this feature the switching must be controlled by your own script, such as one that calculates with their public key which NOMP uses to derive an address for any coin needed to be paid out.
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.
#### Attack Mitigation #### 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://suchpool.pw
* http://hashfaster.com * http://hashfaster.com
* http://miningpoolhub.com * http://miningpoolhub.com
* http://teamdoge.com
* http://miningwith.us
* http://kryptochaos.com * 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 Usage
===== =====
@ -164,6 +168,11 @@ Explanation for each field:
will also be logged. */ will also be logged. */
"logLevel": "debug", //or "warning", "error" "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 /* 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), 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 and the kernel will load balance miners using these forks. Optionally, the 'forks' field
@ -202,65 +211,67 @@ Explanation for each field:
"port": 6379 "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 /* With this switching configuration, you can setup ports that accept miners for work based on
the 'scripts/coinSwitch.js' script which will trigger your proxy pools to switch to the a specific algorithm instead of a specific coin. Miners that connect to these ports are
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
automatically switched a coin determined by the server. The default coin is the first 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 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 Miners connecting to these switching ports must use their public key in the format of
configuration and that payout processing is left up to the server administrator. */ RIPEMD160(SHA256(public-key)). An address for each type of coin is derived from the miner's
"proxy": { public key, and payments are sent to that address. */
"sha256": { "switching": {
"switch1": {
"enabled": false, "enabled": false,
"port": "3333", "algorithm": "sha256",
"diff": 10, "ports": {
"varDiff": { "3333": {
"minDiff": 16, //Minimum difficulty "diff": 10,
"maxDiff": 512, //Network difficulty will be used if it is lower than this "varDiff": {
"targetTime": 15, //Try to get 1 share per this many seconds "minDiff": 16,
"retargetTime": 90, //Check to see if we should retarget every this many seconds "maxDiff": 512,
"variancePercent": 30 //Allow time to very this % from target without retargeting "targetTime": 15,
"retargetTime": 90,
"variancePercent": 30
}
}
} }
}, },
"scrypt": { "switch2": {
"enabled": false, "enabled": false,
"port": "4444", "algorithm": "scrypt",
"diff": 10, "ports": {
"varDiff": { "4444": {
"minDiff": 16, //Minimum difficulty "diff": 10,
"maxDiff": 512, //Network difficulty will be used if it is lower than this "varDiff": {
"targetTime": 15, //Try to get 1 share per this many seconds "minDiff": 16,
"retargetTime": 90, //Check to see if we should retarget every this many seconds "maxDiff": 512,
"variancePercent": 30 //Allow time to very this % from target without retargeting "targetTime": 15,
"retargetTime": 90,
"variancePercent": 30
}
}
} }
}, },
"scrypt-n": { "switch3": {
"enabled": false, "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. */ job broadcast. */
"txRefreshInterval": 20000, "txRefreshInterval": 20000,
/* Some miner software is bugged and will consider the pool offline if it doesn't receive /* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs
anything for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast 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. */ in this many seconds unless we find a new job. Set to zero or remove to disable this. */
"jobRebroadcastTimeout": 55, "jobRebroadcastTimeout": 55,
@ -400,6 +411,10 @@ Description of options:
"password": "mypass", //MySQL db password "password": "mypass", //MySQL db password
"database": "ltc", //MySQL db database name "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 /* 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 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 */ 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` 1. In `config.json` set the port and password for `blockNotifyListener`
2. In your daemon conf file set the `blocknotify` command to use: 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 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 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 #### 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. * 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`. * Remove the dependenices by deleting the `node_modules` directory with `rm -r node_modules`.
* Run `npm update` to force updating/reinstalling of the dependencies. * 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 * [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 * [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 * [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 * [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 * [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing

5
coins/asiccoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "ASICcoin",
"symbol": "ASC",
"algorithm": "sha256"
}

5
coins/battlecoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Battlecoin",
"symbol": "BCX",
"algorithm": "sha256"
}

5
coins/benjamins.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Benjamins",
"symbol": "BEN",
"algorithm": "sha256"
}

5
coins/betacoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Betacoin",
"symbol": "BET",
"algorithm": "sha256"
}

5
coins/devcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Devcoin",
"symbol": "DVC",
"algorithm": "sha256"
}

View File

@ -1,7 +0,0 @@
{
"name": "Ecoin",
"symbol": "ECN",
"algorithm": "keccak",
"normalHashing": true,
"diffShift": 32
}

5
coins/emark.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "eMark",
"symbol": "DEM",
"algorithm": "sha256"
}

5
coins/fedoracoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "FedoraCoin",
"symbol": "TiPS",
"algorithm": "scrypt"
}

5
coins/fireflycoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Fireflycoin",
"symbol": "FFC",
"algorithm": "sha256"
}

5
coins/freicoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Freicoin",
"symbol": "FRC",
"algorithm": "sha256"
}

5
coins/giarcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Giarcoin",
"symbol": "GIAR",
"algorithm": "scrypt-n"
}

5
coins/globalboost.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "GlobalBoost",
"symbol": "BST",
"algorithm": "scrypt"
}

View File

@ -0,0 +1,5 @@
{
"name": "GlobalDenomination",
"symbol": "GDN",
"algorithm": "x11"
}

5
coins/guarantcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Guarantcoin",
"symbol": "GTC",
"algorithm": "scrypt-n"
}

5
coins/ixcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Ixcoin",
"symbol": "IXC",
"algorithm": "sha256"
}

5
coins/joulecoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Joulecoin",
"symbol": "XJO",
"algorithm": "sha256"
}

5
coins/mazacoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Mazacoin",
"symbol": "MZC",
"algorithm": "sha256"
}

5
coins/mintcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Mintcoin",
"symbol": "MINT",
"algorithm": "scrypt"
}

View File

@ -0,0 +1,5 @@
{
"name": "OpenSourcecoin",
"symbol": "OSC",
"algorithm": "sha256"
}

View File

@ -1,5 +1,6 @@
{ {
"name": "Quarkcoin", "name": "Quarkcoin",
"symbol": "QRK", "symbol": "QRK",
"algorithm": "quark" "algorithm": "quark",
"mposDiffMultiplier": 256
} }

5
coins/starcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Starcoin",
"symbol": "STR",
"algorithm": "scrypt"
}

5
coins/takcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Takcoin",
"symbol": "TAK",
"algorithm": "sha256"
}

5
coins/teacoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Teacoin",
"symbol": "TEA",
"algorithm": "sha256"
}

5
coins/tekcoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Tekcoin",
"symbol": "TEK",
"algorithm": "sha256"
}

5
coins/tigercoin.json Normal file
View File

@ -0,0 +1,5 @@
{
"name": "Tigercoin",
"symbol": "TGC",
"algorithm": "sha256"
}

View File

@ -1,6 +1,8 @@
{ {
"logLevel": "debug", "logLevel": "debug",
"cliPort": 17117,
"clustering": { "clustering": {
"enabled": true, "enabled": true,
"forks": "auto" "forks": "auto"
@ -26,47 +28,47 @@
"port": 6379 "port": 6379
}, },
"blockNotifyListener": { "switching": {
"enabled": false, "switch1": {
"port": 8117,
"password": "test"
},
"coinSwitchListener": {
"enabled": false,
"host": "127.0.0.1",
"port": 8118,
"password": "test"
},
"proxy": {
"sha256": {
"enabled": false, "enabled": false,
"port": "3333", "algorithm": "sha256",
"diff": 10, "ports": {
"varDiff": { "3333": {
"minDiff": 16, "diff": 10,
"maxDiff": 512, "varDiff": {
"targetTime": 15, "minDiff": 16,
"retargetTime": 90, "maxDiff": 512,
"variancePercent": 30 "targetTime": 15,
"retargetTime": 90,
"variancePercent": 30
}
}
} }
}, },
"scrypt": { "switch2": {
"enabled": false, "enabled": false,
"port": "4444", "algorithm": "scrypt",
"diff": 10, "ports": {
"varDiff": { "4444": {
"minDiff": 16, "diff": 10,
"maxDiff": 512, "varDiff": {
"targetTime": 15, "minDiff": 16,
"retargetTime": 90, "maxDiff": 512,
"variancePercent": 30 "targetTime": 15,
"retargetTime": 90,
"variancePercent": 30
}
}
} }
}, },
"scrypt-n": { "switch3": {
"enabled": false, "enabled": false,
"port": "5555" "algorithm": "x11",
"ports": {
"5555": {
"diff": 0.001
}
}
} }
}, },

219
init.js
View File

@ -5,8 +5,7 @@ var cluster = require('cluster');
var async = require('async'); var async = require('async');
var PoolLogger = require('./libs/logUtil.js'); var PoolLogger = require('./libs/logUtil.js');
var BlocknotifyListener = require('./libs/blocknotifyListener.js'); var CliListener = require('./libs/cliListener.js');
var CoinswitchListener = require('./libs/coinswitchListener.js');
var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js'); var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js');
var PoolWorker = require('./libs/poolWorker.js'); var PoolWorker = require('./libs/poolWorker.js');
var PaymentProcessor = require('./libs/paymentProcessor.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 portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'})));
var poolConfigs;
var logger = new PoolLogger({ var logger = new PoolLogger({
@ -82,18 +82,68 @@ if (cluster.isWorker){
var buildPoolConfigs = function(){ var buildPoolConfigs = function(){
var configs = {}; var configs = {};
var configDir = 'pool_configs/'; var configDir = 'pool_configs/';
var poolConfigFiles = [];
/* Get filenames of pool config json files that are enabled */
fs.readdirSync(configDir).forEach(function(file){ fs.readdirSync(configDir).forEach(function(file){
if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return; if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return;
var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'}))); var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'})));
if (!poolOptions.enabled) return; 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)){ 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; return;
} }
var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'})));
poolOptions.coin = coinProfile; 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; configs[poolOptions.coin.name] = poolOptions;
if (!(coinProfile.algorithm in algos)){ 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){ Object.keys(poolConfigs).forEach(function(coin){
var p = poolConfigs[coin]; var p = poolConfigs[coin];
@ -130,6 +180,7 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
return; return;
} }
var serializedConfigs = JSON.stringify(poolConfigs); var serializedConfigs = JSON.stringify(poolConfigs);
var numForks = (function(){ var numForks = (function(){
@ -185,51 +236,111 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
}; };
var startBlockListener = function(portalConfig){ var startCliListener = function(){
//block notify options
//setup block notify here and use IPC to tell appropriate pools var cliPort = portalConfig.cliPort;
var listener = new BlocknotifyListener(portalConfig.blockNotifyListener);
var listener = new CliListener(cliPort);
listener.on('log', function(text){ listener.on('log', function(text){
logger.debug('Master', 'Blocknotify', text); logger.debug('Master', 'CLI', text);
}); }).on('command', function(command, params, options, reply){
listener.on('hash', function(message){
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; switch(command){
Object.keys(cluster.workers).forEach(function(id) { case 'blocknotify':
cluster.workers[id].send(ipcMessage); Object.keys(cluster.workers).forEach(function(id) {
}); cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]});
});
}); reply('Pool workers notified');
listener.start(); 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();
}; };
// var processCoinSwitchCommand = function(params, options, reply){
// Receives authenticated events from coin switch listener and triggers proxy
// to swtich to a new coin. var logSystem = 'CLI';
// var logComponent = 'coinswitch';
var startCoinswitchListener = function(portalConfig){
var listener = new CoinswitchListener(portalConfig.coinSwitchListener); var replyError = function(msg){
listener.on('log', function(text){ reply(msg);
logger.debug('Master', 'Coinswitch', text); logger.error(logSystem, logComponent, msg);
}); };
listener.on('switchcoin', function(message){
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash}; if (!params[0]) {
Object.keys(cluster.workers).forEach(function(id) { replyError('Coin name required');
cluster.workers[id].send(ipcMessage); return;
}); }
var ipcMessage = {
type:'switch', if (!params[1] && !options.algorithm){
coin: message.coin replyError('If switch key is not provided then algorithm options must be specified');
}; return;
Object.keys(cluster.workers).forEach(function(id) { }
cluster.workers[id].send(ipcMessage); 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 //block notify options
//setup block notify here and use IPC to tell appropriate pools //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; var enabledForAny = false;
for (var pool in poolConfigs){ 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; 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){ if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){
logger.error('Master', 'Profit', 'Profit auto switching disabled'); //logger.error('Master', 'Profit', 'Profit auto switching disabled');
return; return;
} }
@ -318,20 +429,18 @@ var startProfitSwitch = function(portalConfig, poolConfigs){
(function init(){ (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); startCliListener();
startProfitSwitch(portalConfig, poolConfigs);
})(); })();

View File

@ -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;

42
libs/cliListener.js Normal file
View File

@ -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;

View File

@ -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;

View File

@ -7,33 +7,21 @@ module.exports = function(logger, poolConfig){
var connection; var connection;
var logIdentify = 'MySQL'; var logIdentify = 'MySQL';
var logComponent = coin; var logComponent = coin;
function connect(){ function connect(){
connection = mysql.createConnection({
connection = mysql.createPool({
host: mposConfig.host, host: mposConfig.host,
port: mposConfig.port, port: mposConfig.port,
user: mposConfig.user, user: mposConfig.user,
password: mposConfig.password, password: mposConfig.password,
database: mposConfig.database 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(); connect();
@ -48,8 +36,39 @@ module.exports = function(logger, poolConfig){
JSON.stringify(err)); JSON.stringify(err));
authCallback(false); authCallback(false);
} }
else if (!result[0]) else if (!result[0]){
authCallback(false); 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') else if (mposConfig.stratumAuth === 'worker')
authCallback(true); authCallback(true);
else if (result[0].password === password) else if (result[0].password === password)

View File

@ -2,7 +2,7 @@ var redis = require('redis');
var async = require('async'); var async = require('async');
var Stratum = require('stratum-pool'); var Stratum = require('stratum-pool');
var util = require('stratum-pool/lib/util.js');
module.exports = function(logger){ 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]]; return ['gettransaction', [r.txHash]];
}); });
batchRPCcommand.push(['getaccount', [poolOptions.address]]);
daemon.batchCmd(batchRPCcommand, function(error, txDetails){ daemon.batchCmd(batchRPCcommand, function(error, txDetails){
if (error || !txDetails){ if (error || !txDetails){
@ -224,7 +224,15 @@ function SetupForPool(logger, poolOptions, setupFinished){
return; return;
} }
var addressAccount;
txDetails.forEach(function(tx, i){ txDetails.forEach(function(tx, i){
if (i === txDetails.length - 1){
addressAccount = tx.result;
return;
}
var round = rounds[i]; var round = rounds[i];
if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){ 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, 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 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. */ 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) { if (!magnitude) {
magnitude = roundMagnitude; magnitude = roundMagnitude;
@ -303,7 +311,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
callback('Check finished - no confirmed or orphaned blocks found'); callback('Check finished - no confirmed or orphaned blocks found');
} }
else{ 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 /* Does a batch redis call to get shares contributed to each round. Then calculates the reward
amount owned to each miner for each round. */ amount owned to each miner for each round. */
function(rounds, magnitude, callback){ function(rounds, magnitude, addressAccount, callback){
var shareLookups = rounds.map(function(r){ var shareLookups = rounds.map(function(r){
@ -356,11 +364,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
var reward = round.reward * (1 - processingConfig.feePercent); var reward = round.reward * (1 - processingConfig.feePercent);
var totalShares = Object.keys(workerShares).reduce(function(p, c){ var totalShares = Object.keys(workerShares).reduce(function(p, c){
return p + parseInt(workerShares[c]) return p + parseFloat(workerShares[c])
}, 0); }, 0);
for (var worker in workerShares){ for (var worker in workerShares){
var percent = parseInt(workerShares[worker]) / totalShares; var percent = parseFloat(workerShares[worker]) / totalShares;
var workerRewardTotal = Math.floor(reward * percent); var workerRewardTotal = Math.floor(reward * percent);
if (!(worker in workerRewards)) workerRewards[worker] = 0; if (!(worker in workerRewards)) workerRewards[worker] = 0;
workerRewards[worker] += workerRewardTotal; 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*/ /* 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); 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, 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) 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 :) //number of satoshis in a single coin unit - this can be different for coins so we calculate it :)
daemon.cmd('getbalance', [addressAccount || ''], function(results){
daemon.cmd('getbalance', [''], function(results){
var totalBalance = results[0].response * magnitude; var totalBalance = results[0].response * magnitude;
var toBePaid = 0; 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 /* TODO: Need to convert all these variables into whole coin units before displaying because
humans aren't good at reading satoshi units. */ humans aren't good at reading satoshi units. */
callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' + callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' +
toBePaid + ' and collect ' + feeAmountToBeCollected + ' as fees' + (toBePaid/magnitude) + ' and collect ' + (feeAmountToBeCollected/magnitude) + ' as fees' +
' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver + ' but only have ' + (totalBalance/magnitude) + '. Left over balance would be ' + (balanceLeftOver/magnitude) +
', needs to be at least ' + minReserveSatoshis); ', needs to be at least ' + (minReserveSatoshis/magnitude));
return; return;
} }
@ -495,7 +502,8 @@ function SetupForPool(logger, poolOptions, setupFinished){
} }
})(); })();
movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]); 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 = []; var finalRedisCommands = [];
@ -522,23 +530,23 @@ function SetupForPool(logger, poolOptions, setupFinished){
finalRedisCommands.push(['bgsave']); 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 */ /* Save final redis cleanout commands in case something goes wrong during payments */
redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) { redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) {
if (error){ if (error){
callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error)); callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error));
return; 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 //This does the final all-or-nothing atom transaction if block deamon sent payments
var finalizeRedisTx = function(){ var finalizeRedisTx = function(){
@ -563,14 +571,19 @@ function SetupForPool(logger, poolOptions, setupFinished){
var totalAmountUnits = 0; var totalAmountUnits = 0;
for (var address in workerPayments){ for (var address in workerPayments){
var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision); 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; totalAmountUnits += coinUnits;
} }
logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts)); logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts));
processingPayments = true; processingPayments = true;
daemon.cmd('sendmany', ['', addressAmounts], function(results){ daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){
if (results[0].error){ if (results[0].error){
callback('Check finished - error with sendmany ' + JSON.stringify(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 feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision));
var poolFees = feeAmountUnits - results[0].response.fee; 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){ if (results[0].error){
callback('Check finished - error with move ' + JSON.stringify(results[0].error)); callback('Check finished - error with move ' + JSON.stringify(results[0].error));
return; 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(){ var withdrawalProfit = function(){
if (!processingConfig.feeWithdrawalThreshold) return; if (!processingConfig.feeWithdrawalThreshold) return;
@ -641,7 +661,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){ daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){
if (results[0].error){ 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)); + JSON.stringify(results[0].error));
return; return;
} }
@ -652,5 +672,4 @@ function SetupForPool(logger, poolOptions, setupFinished){
}); });
}; };
}
};

View File

@ -18,6 +18,8 @@ module.exports = function(logger){
var proxySwitch = {}; var proxySwitch = {};
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host);
//Handle messages from master process sent via IPC //Handle messages from master process sent via IPC
process.on('message', function(message) { process.on('message', function(message) {
switch(message.type){ switch(message.type){
@ -42,26 +44,21 @@ module.exports = function(logger){
break; break;
// IPC message for pool switching // IPC message for pool switching
case 'switch': case 'coinswitch':
var logSystem = 'Proxy'; var logSystem = 'Proxy';
var logComponent = 'Switch'; var logComponent = 'Switch';
var logSubCat = 'Thread ' + (parseInt(forkId) + 1); var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
var messageCoin = message.coin.toLowerCase(); var switchName = message.switchName;
var newCoin = Object.keys(pools).filter(function(p){
return p.toLowerCase() === messageCoin;
})[0];
if (!newCoin){ var newCoin = message.coin;
logger.debug(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin);
break;
}
var algo = poolConfigs[newCoin].coin.algorithm; var algo = poolConfigs[newCoin].coin.algorithm;
var newPool = pools[newCoin]; var newPool = pools[newCoin];
var oldCoin = proxySwitch[algo].currentPool; var oldCoin = proxySwitch[switchName].currentPool;
var oldPool = pools[oldCoin]; var oldPool = pools[oldCoin];
var proxyPort = proxySwitch[algo].port; var proxyPorts = Object.keys(proxySwitch[switchName].ports);
if (newCoin == oldCoin) { if (newCoin == oldCoin) {
logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin); logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin);
@ -74,25 +71,23 @@ module.exports = function(logger){
oldPool.relinquishMiners( oldPool.relinquishMiners(
function (miner, cback) { function (miner, cback) {
// relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there. // 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) { function (clients) {
newPool.attachMiners(clients); newPool.attachMiners(clients);
} }
); );
proxySwitch[algo].currentPool = newCoin; proxySwitch[switchName].currentPool = newCoin;
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host) redisClient.hset('proxyState', algo, newCoin, function(error, obj) {
redisClient.on('ready', function(){ if (error) {
redisClient.hset('proxyState', algo, newCoin, function(error, obj) { logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err))
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);
else { }
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo);
}
});
}); });
} }
break; break;
} }
@ -117,9 +112,9 @@ module.exports = function(logger){
//Functions required for MPOS compatibility //Functions required for MPOS compatibility
if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){ 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); mposCompat.handleAuth(workerName, password, authCallback);
}; };
@ -135,12 +130,32 @@ module.exports = function(logger){
//Functions required for internal payment processing //Functions required for internal payment processing
else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){ 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) if (shareProcessing.internal.validateWorkerAddress !== true)
authCallback(true); authCallback(true);
else { 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){ pool.daemon.cmd('validateaddress', [workerName], function(results){
var isValid = results.filter(function(r){return r.response.isvalid}).length > 0; var isValid = results.filter(function(r){return r.response.isvalid}).length > 0;
authCallback(isValid); authCallback(isValid);
@ -153,8 +168,8 @@ module.exports = function(logger){
}; };
} }
var authorizeFN = function (ip, workerName, password, callback) { var authorizeFN = function (ip, port, workerName, password, callback) {
handlers.auth(workerName, password, function(authorized){ handlers.auth(port, workerName, password, function(authorized){
var authString = authorized ? 'Authorized' : 'Unauthorized '; 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 logComponent = 'Setup';
var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 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 // on the last pool it was using when reloaded or restarted
// //
logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); 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
// /*redisClient.on('error', function(err){
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err);
// routed into instead. });*/
//
if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) { redisClient.hgetall("proxyState", function(error, obj) {
proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff); if (error || obj == null) {
proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff; //logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis');
} }
Object.keys(pools).forEach(function (coinName) { else {
var a = poolConfigs[coinName].coin.algorithm; proxyState = obj;
var p = pools[coinName]; logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis');
if (a === algorithm) { }
p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff);
//
// 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 Object.keys(proxySwitch[switchName].ports).forEach(function(port){
+ ' routing to ' + currentPool); 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); pools[currentPool].getStratumServer().handleNewClient(socket);
}).listen(parseInt(proxySwitch[algorithm].port), function() { }).listen(parseInt(port), function() {
logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName
+ ' into ' + proxySwitch[algorithm].currentPool); + '" listening for ' + algorithm
+ ' on port ' + port
+ ' into ' + proxySwitch[switchName].currentPool);
}); });
} proxySwitch[switchName].servers.push(f);
else { });
logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.');
}
}); }
else {
//logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.');
}
}); });
}).on('error', function(err){
logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err);
}); });
} }

View File

@ -420,31 +420,28 @@ module.exports = function(logger){
}; };
this.getDaemonInfoForCoin = function(symbol, cfg, callback){ this.getDaemonInfoForCoin = function(symbol, cfg, callback){
var daemon = new Stratum.daemon.interface([cfg]); var daemon = new Stratum.daemon.interface([cfg]);
daemon.once('online', function(){ daemon.on('error', function(error){
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){
logger.error(logSystem, symbol, JSON.stringify(error)); logger.error(logSystem, symbol, JSON.stringify(error));
callback(null); // fail gracefully for each coin callback(null); // fail gracefully for each coin
}).init(); }).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).forEach(function(algo){
Object.keys(profitStatus[algo]).forEach(function(symbol){ Object.keys(profitStatus[algo]).forEach(function(symbol){
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol]; var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000))); coinStatus.blocksPerMhPerHour = 86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000));
coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour); coinStatus.coinsPerMhPerHour = coinStatus.reward * coinStatus.blocksPerMhPerHour;
}); });
}); });
callback(null); callback(null);
@ -467,7 +464,7 @@ module.exports = function(logger){
var bestExchange; var bestExchange;
var bestCoin; var bestCoin;
var bestBtcPerMhPerHour = new Number(0); var bestBtcPerMhPerHour = 0;
Object.keys(profitStatus[algo]).forEach(function(symbol) { Object.keys(profitStatus[algo]).forEach(function(symbol) {
var coinStatus = profitStatus[algo][symbol]; var coinStatus = profitStatus[algo][symbol];
@ -475,7 +472,7 @@ module.exports = function(logger){
Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){ Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){
var exchangeData = coinStatus.exchangeInfo[exchange]; var exchangeData = coinStatus.exchangeInfo[exchange];
if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){ 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){ if (btcPerMhPerHour > bestBtcPerMhPerHour){
bestBtcPerMhPerHour = btcPerMhPerHour; bestBtcPerMhPerHour = btcPerMhPerHour;
bestExchange = exchange; 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'); 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')){ 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){ if (btcPerMhPerHour > bestBtcPerMhPerHour){
bestBtcPerMhPerHour = btcPerMhPerHour; bestBtcPerMhPerHour = btcPerMhPerHour;
bestExchange = exchange; 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'); 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({ var client = net.connect(portalConfig.cliPort, function () {
password: portalConfig.coinSwitchListener.password, client.write(JSON.stringify({
coin: bestCoin command: 'coinswitch',
}) + '\n'); 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));
});
}); });
}; };

View File

@ -148,7 +148,11 @@ module.exports = function(logger, portalConfig, poolConfigs){
symbol: poolConfigs[coinName].coin.symbol.toUpperCase(), symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
algorithm: poolConfigs[coinName].coin.algorithm, algorithm: poolConfigs[coinName].coin.algorithm,
hashrates: replies[i + 1], 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: { blocks: {
pending: replies[i + 3], pending: replies[i + 3],
confirmed: replies[i + 4], confirmed: replies[i + 4],
@ -191,9 +195,10 @@ module.exports = function(logger, portalConfig, poolConfigs){
else else
coinStats.workers[worker] = workerShares; coinStats.workers[worker] = workerShares;
}); });
var shareMultiplier = algos[coinStats.algorithm].multiplier || 0;
var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier;
coinStats.hashrate = hashratePre | 0; coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;
coinStats.workerCount = Object.keys(coinStats.workers).length; coinStats.workerCount = Object.keys(coinStats.workers).length;
portalStats.global.workers += coinStats.workerCount; portalStats.global.workers += coinStats.workerCount;

View File

@ -3,18 +3,24 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var async = require('async'); var async = require('async');
var watch = require('node-watch');
var redis = require('redis');
var dot = require('dot'); var dot = require('dot');
var express = require('express'); var express = require('express');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var compress = require('compression'); 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'); var api = require('./api.js');
module.exports = function(logger){ module.exports = function(logger){
dot.templateSettings.strip = false;
var portalConfig = JSON.parse(process.env.portalConfig); var portalConfig = JSON.parse(process.env.portalConfig);
var poolConfigs = JSON.parse(process.env.pools); var poolConfigs = JSON.parse(process.env.pools);
@ -31,8 +37,10 @@ module.exports = function(logger){
'home.html': '', 'home.html': '',
'getting_started.html': 'getting_started', 'getting_started.html': 'getting_started',
'stats.html': 'stats', 'stats.html': 'stats',
'tbs.html': 'tbs',
'api.html': 'api', 'api.html': 'api',
'admin.html': 'admin' 'admin.html': 'admin',
'mining_key.html': 'mining_key'
}; };
var pageTemplates = {}; var pageTemplates = {};
@ -40,6 +48,9 @@ module.exports = function(logger){
var pageProcessed = {}; var pageProcessed = {};
var indexesProcessed = {}; var indexesProcessed = {};
var keyScriptTemplate = '';
var keyScriptProcessed = '';
var processTemplates = function(){ var processTemplates = function(){
@ -112,6 +123,87 @@ module.exports = function(logger){
setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000); 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){ var getPage = function(pageId){
if (pageId in pageProcessed){ if (pageId in pageProcessed){
@ -146,6 +238,10 @@ module.exports = function(logger){
next(); next();
}); });
app.get('/key.html', function(reg, res, next){
res.end(keyScriptProcessed);
});
app.get('/:page', route); app.get('/:page', route);
app.get('/', route); app.get('/', route);

View File

@ -1,6 +1,6 @@
{ {
"name": "node-open-mining-portal", "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", "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool",
"keywords": [ "keywords": [
"stratum", "stratum",

View File

@ -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();
});

View File

@ -16,76 +16,69 @@ Simple lightweight & fast - a more efficient block notify script in pure C.
(may also work as coin switch) (may also work as coin switch)
Platforms : Linux,BSD,Solaris (mostly OS independent) Platforms : Linux, BSD, Solaris (mostly OS independent)
Build with: Build with:
gcc blocknotify.c -o blocknotify gcc blocknotify.c -o blocknotify
Usage in daemon coin.conf Example usage in daemon coin.conf using default NOMP CLI port of 17117
blocknotify="/bin/blocknotify 127.0.0.1:8117 mySuperSecurePassword dogecoin %s" 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 main(int argc, char **argv)
{ {
int sockfd,n; int sockfd,n;
struct sockaddr_in servaddr,cliaddr; struct sockaddr_in servaddr, cliaddr;
char sendline[1000]; char sendline[1000];
char recvline[1000]; char recvline[1000];
char host[200]; char host[200];
char *p,*arg,*errptr; char *p, *arg, *errptr;
int port; int port;
if (argc < 4) if (argc < 3)
{ {
// print help // print help
printf("NOMP pool block notify\n usage: <host:port> <password> <coin> <block>\n"); printf("NOMP pool block notify\n usage: <host:port> <coin> <block>\n");
exit(1); exit(1);
} }
strncpy(host,argv[1],(sizeof(host)-1)); strncpy(host, argv[1], (sizeof(host)-1));
p=host; p = host;
if ( (arg=strchr(p,':')) ) if ( (arg = strchr(p,':')) )
{ {
*arg='\0'; *arg = '\0';
errno=0; // reset errno errno = 0; // reset errno
port=strtol(++arg,&errptr,10); port = strtol(++arg, &errptr, 10);
if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); } if ( (errno != 0) || (errptr == arg) )
// if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now {
// printf("host %s:%d\n",host,port); 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); sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bzero(&servaddr,sizeof(servaddr)); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET; servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr=inet_addr(host); servaddr.sin_addr.s_addr = inet_addr(host);
servaddr.sin_port=htons(port); servaddr.sin_port = htons(port);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
int result = send(sockfd,sendline,strlen(sendline),0); int result = send(sockfd, sendline, strlen(sendline), 0);
close(sockfd); close(sockfd);
if(result == -1) { if(result == -1) {
printf("Error sending: %i\n",errno); printf("Error sending: %i\n", errno);
exit(-1); exit(-1);
} }
exit(0); exit(0);
} }

37
scripts/cli.js Normal file
View File

@ -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 () {
});

View File

@ -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();
});

View File

@ -42,7 +42,13 @@
<li class="{{? it.selected === 'stats' }}pure-menu-selected{{?}}"> <li class="{{? it.selected === 'stats' }}pure-menu-selected{{?}}">
<a class="hot-swapper" href="/stats"> <a class="hot-swapper" href="/stats">
<i class="fa fa-bar-chart-o"></i>&nbsp; <i class="fa fa-bar-chart-o"></i>&nbsp;
Stats Graph Stats
</a>
</li>
<li class="{{? it.selected === 'tbs' }}pure-menu-selected{{?}}">
<a class="hot-swapper" href="/tbs">
<i class="fa fa-table"></i>&nbsp;
Tab Stats
</a> </a>
</li> </li>
<li class="{{? it.selected === 'api' }}pure-menu-selected{{?}}"> <li class="{{? it.selected === 'api' }}pure-menu-selected{{?}}">

2798
website/key.html Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,10 @@
<div> <div>
API Docs here API Docs here
<ul>
<li>/stats - raw json statistic</li>
<li>/pool_stats - historical time per pool json </li>
<li>/live_stats - live stats </li>
</ul>
</div> </div>

View File

@ -0,0 +1,25 @@
<style>
#miningKeyPage{
margin: 15px;
}
#keyFrame{
padding: 0;
border: 0;
width: 100%;
height: 750px;
display: block;
}
</style>
<div id="miningKeyPage">
<p>
This script run client-side (in your browser). For maximum security <a href="/key.html" download="key.html">download</a> the script and run it locally and
offline in a modern web browser.
</p>
<iframe id="keyFrame" src="/key.html">
</iframe>
</div>

65
website/pages/tbs.html Normal file
View File

@ -0,0 +1,65 @@
<style>
#topCharts {
padding: 18px;
}
#topCharts > div > div > svg {
display: block;
height: 280px;
}
.chartWrapper {
border: solid 1px #c7c7c7;
border-radius: 5px;
padding: 5px;
margin-bottom: 18px;
}
.chartLabel {
font-size: 1.2em;
text-align: center;
padding: 4px;
}
.chartHolder {
}
table {
width: 100%;
}
</style>
<table class="pure-table">
<thead>
<tr>
<th>Pool</th>
<th>Algo</th>
<th>Workers</th>
<th>Valid Shares</th>
<th>Invalid Shares</th>
<th>Total Blocks</th>
<th>Pending</th>
<th>Confirmed</th>
<th>Orphaned</th>
<th>Hashrate</th>
</tr>
</tr>
</thead>
{{ for(var pool in it.stats.pools) { }}
<tr class="pure-table-odd">
<td>{{=it.stats.pools[pool].name}}</td>
<td>{{=it.stats.pools[pool].algorithm}}</td>
<td>{{=Object.keys(it.stats.pools[pool].workers).length}}</td>
<td>{{=it.stats.pools[pool].poolStats.validShares}}</td>
<td>{{=it.stats.pools[pool].poolStats.invalidShares}}</td>
<td>{{=it.stats.pools[pool].poolStats.validBlocks}}</td>
<td>{{=it.stats.pools[pool].blocks.pending}}</td>
<td>{{=it.stats.pools[pool].blocks.confirmed}}</td>
<td>{{=it.stats.pools[pool].blocks.orphaned}}</td>
<td>{{=it.stats.pools[pool].hashrateString}}</td>
</tr>
{{ } }}
</table>