2014-02-20 15:13:50 -08:00
var fs = require ( 'fs' ) ;
2014-03-31 12:36:31 -07:00
var path = require ( 'path' ) ;
2014-02-28 19:12:59 -08:00
var os = require ( 'os' ) ;
var cluster = require ( 'cluster' ) ;
2014-04-02 13:09:57 -07:00
var async = require ( 'async' ) ;
var PoolLogger = require ( './libs/logUtil.js' ) ;
2014-04-26 15:23:23 -07:00
var CliListener = require ( './libs/cliListener.js' ) ;
2014-03-14 12:02:55 -07:00
var RedisBlocknotifyListener = require ( './libs/redisblocknotifyListener.js' ) ;
2014-04-02 13:09:57 -07:00
var PoolWorker = require ( './libs/poolWorker.js' ) ;
var PaymentProcessor = require ( './libs/paymentProcessor.js' ) ;
var Website = require ( './libs/website.js' ) ;
2014-04-13 09:44:12 -07:00
var ProfitSwitch = require ( './libs/profitSwitch.js' ) ;
2014-04-02 13:09:57 -07:00
var algos = require ( 'stratum-pool/lib/algoProperties.js' ) ;
2014-01-16 11:18:16 -08:00
JSON . minify = JSON . minify || require ( "node-json-minify" ) ;
2014-04-05 13:27:05 -07:00
if ( ! fs . existsSync ( 'config.json' ) ) {
console . log ( 'config.json file does not exist. Read the installation/setup instructions.' ) ;
return ;
}
2014-03-13 14:03:28 -07:00
var portalConfig = JSON . parse ( JSON . minify ( fs . readFileSync ( "config.json" , { encoding : 'utf8' } ) ) ) ;
2014-04-28 14:04:12 -07:00
var poolConfigs ;
2014-03-20 15:25:59 -07:00
2014-02-28 19:12:59 -08:00
2014-03-22 23:16:06 -07:00
var logger = new PoolLogger ( {
2014-03-13 14:03:28 -07:00
logLevel : portalConfig . logLevel
2014-01-14 08:56:06 -08:00
} ) ;
2014-01-13 17:32:54 -08:00
2014-03-22 23:16:06 -07:00
2014-01-13 17:32:54 -08:00
2014-03-20 15:25:59 -07:00
try {
require ( 'newrelic' ) ;
if ( cluster . isMaster )
2014-03-22 23:16:06 -07:00
logger . debug ( 'NewRelic' , 'Monitor' , 'New Relic initiated' ) ;
2014-03-20 15:25:59 -07:00
} catch ( e ) { }
2014-03-09 19:31:58 -07:00
//Try to give process ability to handle 100k concurrent connections
try {
2014-04-05 14:47:00 -07:00
var posix = require ( 'posix' ) ;
try {
posix . setrlimit ( 'nofile' , { soft : 100000 , hard : 100000 } ) ;
}
catch ( e ) {
if ( cluster . isMaster )
logger . warning ( 'POSIX' , 'Connection Limit' , '(Safe to ignore) Must be ran as root to increase resource limits' ) ;
}
2014-03-09 19:31:58 -07:00
}
catch ( e ) {
2014-04-05 14:47:00 -07:00
if ( cluster . isMaster )
logger . debug ( 'POSIX' , 'Connection Limit' , '(Safe to ignore) POSIX module not installed and resource (connection) limit was not raised' ) ;
2014-03-09 19:31:58 -07:00
}
if ( cluster . isWorker ) {
2014-04-13 09:44:12 -07:00
2014-03-09 19:31:58 -07:00
switch ( process . env . workerType ) {
case 'pool' :
2014-03-22 23:16:06 -07:00
new PoolWorker ( logger ) ;
2014-03-09 19:31:58 -07:00
break ;
case 'paymentProcessor' :
2014-03-22 23:16:06 -07:00
new PaymentProcessor ( logger ) ;
2014-03-09 19:31:58 -07:00
break ;
2014-03-12 23:37:27 -07:00
case 'website' :
2014-03-22 23:16:06 -07:00
new Website ( logger ) ;
2014-03-12 23:37:27 -07:00
break ;
2014-04-13 09:44:12 -07:00
case 'profitSwitch' :
new ProfitSwitch ( logger ) ;
break ;
2014-03-09 19:31:58 -07:00
}
2014-01-13 17:32:54 -08:00
2014-03-09 19:31:58 -07:00
return ;
2014-04-06 20:11:53 -07:00
}
2014-02-27 15:59:49 -08:00
2014-02-28 19:12:59 -08:00
2014-03-09 19:31:58 -07:00
//Read all pool configs from pool_configs and join them with their coin profile
var buildPoolConfigs = function ( ) {
var configs = { } ;
2014-03-31 12:36:31 -07:00
var configDir = 'pool_configs/' ;
2014-04-26 15:23:23 -07:00
var poolConfigFiles = [ ] ;
/* Get filenames of pool config json files that are enabled */
2014-03-31 12:36:31 -07:00
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' } ) ) ) ;
2014-03-28 05:42:55 -07:00
if ( ! poolOptions . enabled ) return ;
2014-04-26 15:23:23 -07:00
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 ;
2014-03-09 19:31:58 -07:00
if ( ! fs . existsSync ( coinFilePath ) ) {
2014-04-26 15:23:23 -07:00
logger . error ( 'Master' , poolOptions . coinFileName , 'could not find file: ' + coinFilePath ) ;
2014-03-09 19:31:58 -07:00
return ;
}
2014-02-28 19:12:59 -08:00
2014-03-09 19:31:58 -07:00
var coinProfile = JSON . parse ( JSON . minify ( fs . readFileSync ( coinFilePath , { encoding : 'utf8' } ) ) ) ;
poolOptions . coin = coinProfile ;
2014-04-26 15:23:23 -07:00
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 ;
}
2014-04-08 11:23:48 -07:00
configs [ poolOptions . coin . name ] = poolOptions ;
2014-04-02 13:09:57 -07:00
if ( ! ( coinProfile . algorithm in algos ) ) {
logger . error ( 'Master' , coinProfile . name , 'Cannot run a pool for unsupported algorithm "' + coinProfile . algorithm + '"' ) ;
2014-04-08 11:23:48 -07:00
delete configs [ poolOptions . coin . name ] ;
2014-04-02 13:09:57 -07:00
}
2014-03-09 19:31:58 -07:00
} ) ;
return configs ;
} ;
2014-02-27 15:59:49 -08:00
2014-01-13 17:32:54 -08:00
2014-03-09 19:31:58 -07:00
2014-04-28 14:04:12 -07:00
var spawnPoolWorkers = function ( ) {
2014-02-28 19:12:59 -08:00
2014-03-27 10:52:54 -07:00
Object . keys ( poolConfigs ) . forEach ( function ( coin ) {
var p = poolConfigs [ coin ] ;
2014-04-08 10:38:19 -07:00
2014-04-08 10:41:40 -07:00
if ( ! Array . isArray ( p . daemons ) || p . daemons . length < 1 ) {
2014-04-08 10:38:19 -07:00
logger . error ( 'Master' , coin , 'No daemons configured so a pool cannot be started for this coin.' ) ;
delete poolConfigs [ coin ] ;
}
2014-03-27 10:52:54 -07:00
} ) ;
2014-03-30 02:08:19 -07:00
if ( Object . keys ( poolConfigs ) . length === 0 ) {
logger . warning ( 'Master' , 'PoolSpawner' , 'No pool configs exists or are enabled in pool_configs folder. No pools spawned.' ) ;
return ;
}
2014-04-26 15:23:23 -07:00
2014-03-27 10:52:54 -07:00
var serializedConfigs = JSON . stringify ( poolConfigs ) ;
2014-02-27 15:59:49 -08:00
2014-02-28 19:12:59 -08:00
var numForks = ( function ( ) {
2014-03-09 19:31:58 -07:00
if ( ! portalConfig . clustering || ! portalConfig . clustering . enabled )
2014-02-28 19:12:59 -08:00
return 1 ;
2014-03-09 19:31:58 -07:00
if ( portalConfig . clustering . forks === 'auto' )
2014-02-28 19:12:59 -08:00
return os . cpus ( ) . length ;
2014-03-09 19:31:58 -07:00
if ( ! portalConfig . clustering . forks || isNaN ( portalConfig . clustering . forks ) )
2014-02-28 19:12:59 -08:00
return 1 ;
2014-03-09 19:31:58 -07:00
return portalConfig . clustering . forks ;
2014-02-28 19:12:59 -08:00
} ) ( ) ;
2014-01-13 17:32:54 -08:00
2014-04-15 15:38:51 -07:00
var poolWorkers = { } ;
2014-01-13 17:32:54 -08:00
2014-03-09 19:31:58 -07:00
var createPoolWorker = function ( forkId ) {
2014-03-06 12:46:01 -08:00
var worker = cluster . fork ( {
2014-03-24 13:00:18 -07:00
workerType : 'pool' ,
forkId : forkId ,
pools : serializedConfigs ,
portalConfig : JSON . stringify ( portalConfig )
2014-03-06 12:46:01 -08:00
} ) ;
2014-04-15 15:38:51 -07:00
worker . forkId = forkId ;
worker . type = 'pool' ;
poolWorkers [ forkId ] = worker ;
2014-03-09 19:31:58 -07:00
worker . on ( 'exit' , function ( code , signal ) {
2014-03-22 23:46:59 -07:00
logger . error ( 'Master' , 'PoolSpanwer' , 'Fork ' + forkId + ' died, spawning replacement worker...' ) ;
2014-03-11 20:47:14 -07:00
setTimeout ( function ( ) {
createPoolWorker ( forkId ) ;
} , 2000 ) ;
2014-04-15 15:38:51 -07:00
} ) . on ( 'message' , function ( msg ) {
switch ( msg . type ) {
case 'banIP' :
Object . keys ( cluster . workers ) . forEach ( function ( id ) {
if ( cluster . workers [ id ] . type === 'pool' ) {
cluster . workers [ id ] . send ( { type : 'banIP' , ip : msg . ip } ) ;
}
} ) ;
break ;
}
2014-03-09 19:31:58 -07:00
} ) ;
} ;
2014-03-22 23:46:59 -07:00
var i = 0 ;
var spawnInterval = setInterval ( function ( ) {
2014-03-09 19:31:58 -07:00
createPoolWorker ( i ) ;
2014-03-22 23:46:59 -07:00
i ++ ;
if ( i === numForks ) {
clearInterval ( spawnInterval ) ;
2014-03-30 16:04:54 -07:00
logger . debug ( 'Master' , 'PoolSpawner' , 'Spawned ' + Object . keys ( poolConfigs ) . length + ' pool(s) on ' + numForks + ' thread(s)' ) ;
2014-03-22 23:46:59 -07:00
}
} , 250 ) ;
2014-01-13 17:32:54 -08:00
2014-03-09 19:31:58 -07:00
} ;
2014-02-27 15:59:49 -08:00
2014-03-01 17:19:10 -08:00
2014-04-28 14:04:12 -07:00
var startCliListener = function ( ) {
var cliPort = portalConfig . cliPort ;
2014-04-26 15:23:23 -07:00
var listener = new CliListener ( cliPort ) ;
2014-02-28 19:12:59 -08:00
listener . on ( 'log' , function ( text ) {
2014-04-26 15:23:23 -07:00
logger . debug ( 'Master' , 'CLI' , text ) ;
2014-04-28 14:04:12 -07:00
} ) . on ( 'command' , function ( command , params , options , reply ) {
2014-04-26 15:23:23 -07:00
switch ( command ) {
case 'blocknotify' :
Object . keys ( cluster . workers ) . forEach ( function ( id ) {
cluster . workers [ id ] . send ( { type : 'blocknotify' , coin : params [ 0 ] , hash : params [ 1 ] } ) ;
} ) ;
2014-04-28 14:04:12 -07:00
reply ( 'Pool workers notified' ) ;
2014-04-26 15:23:23 -07:00
break ;
case 'coinswitch' :
2014-04-28 14:04:12 -07:00
processCoinSwitchCommand ( params , options , reply ) ;
2014-04-26 15:23:23 -07:00
break ;
2014-04-28 14:04:12 -07:00
case 'reloadpool' :
2014-04-26 15:23:23 -07:00
Object . keys ( cluster . workers ) . forEach ( function ( id ) {
2014-04-28 14:04:12 -07:00
cluster . workers [ id ] . send ( { type : 'reloadpool' , coin : params [ 0 ] } ) ;
2014-04-26 15:23:23 -07:00
} ) ;
2014-04-28 14:04:12 -07:00
reply ( 'reloaded pool ' + params [ 0 ] ) ;
break ;
default :
reply ( 'unrecognized command "' + command + '"' ) ;
break ;
2014-04-26 15:23:23 -07:00
}
} ) . start ( ) ;
2014-03-09 19:31:58 -07:00
} ;
2014-01-15 15:04:19 -08:00
2014-04-28 14:04:12 -07:00
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 } ) ;
2014-04-06 20:11:53 -07:00
} ) ;
} ) ;
2014-04-28 14:04:12 -07:00
reply ( 'Switch message sent to pool workers' ) ;
2014-04-06 20:11:53 -07:00
} ;
2014-04-28 14:04:12 -07:00
var startRedisBlockListener = function ( ) {
2014-03-14 12:02:55 -07:00
//block notify options
//setup block notify here and use IPC to tell appropriate pools
2014-03-15 17:58:28 -07:00
if ( ! portalConfig . redisBlockNotifyListener . enabled ) return ;
2014-03-14 12:02:55 -07:00
var listener = new RedisBlocknotifyListener ( portalConfig . redisBlockNotifyListener ) ;
listener . on ( 'log' , function ( text ) {
2014-03-22 23:16:06 -07:00
logger . debug ( 'Master' , 'blocknotify' , text ) ;
2014-03-14 12:02:55 -07:00
} ) . on ( 'hash' , function ( message ) {
var ipcMessage = { type : 'blocknotify' , coin : message . coin , hash : message . hash } ;
Object . keys ( cluster . workers ) . forEach ( function ( id ) {
cluster . workers [ id ] . send ( ipcMessage ) ;
} ) ;
} ) ;
listener . start ( ) ;
} ;
2014-03-07 15:29:16 -08:00
2014-04-28 14:04:12 -07:00
var startPaymentProcessor = function ( ) {
2014-03-26 14:08:34 -07:00
var enabledForAny = false ;
for ( var pool in poolConfigs ) {
var p = poolConfigs [ pool ] ;
2014-05-02 14:59:46 -07:00
var enabled = p . enabled && p . paymentProcessing && p . paymentProcessing . enabled ;
2014-03-26 14:08:34 -07:00
if ( enabled ) {
enabledForAny = true ;
break ;
}
}
if ( ! enabledForAny )
return ;
2014-03-09 19:31:58 -07:00
var worker = cluster . fork ( {
workerType : 'paymentProcessor' ,
pools : JSON . stringify ( poolConfigs )
} ) ;
worker . on ( 'exit' , function ( code , signal ) {
2014-03-22 23:16:06 -07:00
logger . error ( 'Master' , 'Payment Processor' , 'Payment processor died, spawning replacement...' ) ;
2014-03-11 20:47:14 -07:00
setTimeout ( function ( ) {
2014-03-19 13:24:29 -07:00
startPaymentProcessor ( poolConfigs ) ;
2014-03-12 23:37:27 -07:00
} , 2000 ) ;
} ) ;
} ;
2014-04-28 14:04:12 -07:00
var startWebsite = function ( ) {
2014-03-14 14:56:54 -07:00
2014-03-12 23:37:27 -07:00
if ( ! portalConfig . website . enabled ) return ;
var worker = cluster . fork ( {
workerType : 'website' ,
pools : JSON . stringify ( poolConfigs ) ,
portalConfig : JSON . stringify ( portalConfig )
} ) ;
worker . on ( 'exit' , function ( code , signal ) {
2014-03-22 23:16:06 -07:00
logger . error ( 'Master' , 'Website' , 'Website process died, spawning replacement...' ) ;
2014-03-12 23:37:27 -07:00
setTimeout ( function ( ) {
2014-03-19 13:24:29 -07:00
startWebsite ( portalConfig , poolConfigs ) ;
2014-03-11 20:47:14 -07:00
} , 2000 ) ;
2014-03-09 19:31:58 -07:00
} ) ;
} ;
2014-01-15 15:04:19 -08:00
2014-02-28 19:12:59 -08:00
2014-04-28 14:04:12 -07:00
var startProfitSwitch = function ( ) {
2014-04-13 09:44:12 -07:00
2014-04-23 11:53:19 -07:00
if ( ! portalConfig . profitSwitch || ! portalConfig . profitSwitch . enabled ) {
//logger.error('Master', 'Profit', 'Profit auto switching disabled');
2014-04-13 09:44:12 -07:00
return ;
}
var worker = cluster . fork ( {
workerType : 'profitSwitch' ,
pools : JSON . stringify ( poolConfigs ) ,
portalConfig : JSON . stringify ( portalConfig )
} ) ;
worker . on ( 'exit' , function ( code , signal ) {
logger . error ( 'Master' , 'Profit' , 'Profit switching process died, spawning replacement...' ) ;
setTimeout ( function ( ) {
startWebsite ( portalConfig , poolConfigs ) ;
} , 2000 ) ;
} ) ;
} ;
2014-03-09 19:31:58 -07:00
( function init ( ) {
2014-04-28 14:04:12 -07:00
poolConfigs = buildPoolConfigs ( ) ;
2014-03-09 19:31:58 -07:00
2014-04-28 14:04:12 -07:00
spawnPoolWorkers ( ) ;
2014-03-09 19:31:58 -07:00
2014-04-28 14:04:12 -07:00
startPaymentProcessor ( ) ;
2014-03-09 19:31:58 -07:00
2014-04-28 14:04:12 -07:00
startRedisBlockListener ( ) ;
2014-03-14 12:02:55 -07:00
2014-04-28 14:04:12 -07:00
startWebsite ( ) ;
2014-03-14 12:02:55 -07:00
2014-04-28 14:04:12 -07:00
startProfitSwitch ( ) ;
2014-04-13 09:44:12 -07:00
2014-04-28 14:04:12 -07:00
startCliListener ( ) ;
2014-04-26 15:23:23 -07:00
2014-03-14 12:02:55 -07:00
} ) ( ) ;