mirror of https://github.com/BTCPrivate/z-nomp.git
Updated coin configs
This commit is contained in:
commit
12946fdcf0
|
@ -1,3 +1,5 @@
|
|||
node_modules/
|
||||
.idea/
|
||||
config.json
|
||||
config.json
|
||||
pool_configs/*.json
|
||||
!pool_configs/litecoin_example.json
|
||||
|
|
142
README.md
142
README.md
|
@ -7,7 +7,7 @@ responsive user-friendly front-end website featuring mining instructions, in-dep
|
|||
|
||||
#### Table of Contents
|
||||
* [Features](#features)
|
||||
* [Attack Mitigation](##attack-mitigation)
|
||||
* [Attack Mitigation](#attack-mitigation)
|
||||
* [Security](#security)
|
||||
* [Planned Features](#planned-features)
|
||||
* [Community Support](#community--support)
|
||||
|
@ -45,17 +45,15 @@ coins at once. The pools use clustering to load balance across multiple CPU core
|
|||
|
||||
* For reward/payment processing, shares are inserted into Redis (a fast NoSQL key/value store). The PROP (proportional)
|
||||
reward system is used with [Redis Transactions](http://redis.io/topics/transactions) for secure and super speedy payouts.
|
||||
Each and every share will be rewarded - even for rounds resulting in orphaned blocks.
|
||||
There is zero risk to the pool operator. Shares from rounds resulting in orphaned blocks will be merged into share in the
|
||||
current round so that each and every share will be rewarded
|
||||
|
||||
* This portal does not have user accounts/logins/registrations. Instead, miners simply use their coin address for stratum
|
||||
authentication. A minimalistic HTML5 front-end connects to the portals statistics API to display stats from from each
|
||||
pool such as connected miners, network/pool difficulty/hash rate, etc.
|
||||
|
||||
* Automated switching of connected miners to different pools/coins is also easily done due to the multi-pool architecture
|
||||
of this software. To use this feature the switching must be controlled by your own script, such as one that calculates
|
||||
coin profitability via an API such as CoinChoose.com or CoinWarz.com (or calculated locally using daemon-reported network
|
||||
difficulties and exchange APIs). NOMP's regular payment processing and miner authentication which using coin address as stratum
|
||||
username will obviously not work with this coin switching feature - so you must control those with your own script as well.
|
||||
* Coin-switching ports using coin-networks and crypto-exchange APIs to detect profitability. Miner's connect to these ports
|
||||
with their public key which NOMP uses to derive an address for any coin needed to be paid out.
|
||||
|
||||
|
||||
#### Attack Mitigation
|
||||
|
@ -108,9 +106,15 @@ If your pool uses NOMP let us know and we will list your website here.
|
|||
* http://suchpool.pw
|
||||
* http://hashfaster.com
|
||||
* http://miningpoolhub.com
|
||||
* http://teamdoge.com
|
||||
* http://miningwith.us
|
||||
* http://kryptochaos.com
|
||||
* http://pool.uberpools.org
|
||||
|
||||
* http://uberpools.org
|
||||
* http://onebtcplace.com
|
||||
* http://minr.es
|
||||
* http://mining.theminingpools.com
|
||||
* http://www.omargpools.ca/pools.html
|
||||
* http://pool.trademybit.com/
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
@ -163,7 +167,12 @@ Explanation for each field:
|
|||
/* Specifies the level of log output verbosity. Anything more severy than the level specified
|
||||
will also be logged. */
|
||||
"logLevel": "debug", //or "warning", "error"
|
||||
|
||||
|
||||
|
||||
/* The NOMP CLI (command-line interface) will listen for commands on this port. For example,
|
||||
blocknotify messages are sent to NOMP through this. */
|
||||
"cliPort": 17117,
|
||||
|
||||
/* By default 'forks' is set to "auto" which will spawn one process/fork/worker for each CPU
|
||||
core in your system. Each of these workers will run a separate instance of your pool(s),
|
||||
and the kernel will load balance miners using these forks. Optionally, the 'forks' field
|
||||
|
@ -202,65 +211,67 @@ Explanation for each field:
|
|||
"port": 6379
|
||||
},
|
||||
|
||||
/* With this enabled, the master process listen on the configured port for messages from the
|
||||
'scripts/blockNotify.js' script which your coin daemons can be configured to run when a
|
||||
new block is available. When a blocknotify message is received, the master process uses
|
||||
IPC (inter-process communication) to notify each thread about the message. Each thread
|
||||
then sends the message to the appropriate coin pool. See "Setting up blocknotify" below to
|
||||
set up your daemon to use this feature. */
|
||||
"blockNotifyListener": {
|
||||
"enabled": true,
|
||||
"port": 8117,
|
||||
"password": "test"
|
||||
},
|
||||
|
||||
/* With this enabled, the master process will listen on the configured port for messages from
|
||||
the 'scripts/coinSwitch.js' script which will trigger your proxy pools to switch to the
|
||||
specified coin (non-case-sensitive). This setting is used in conjuction with the proxy
|
||||
feature below. */
|
||||
"coinSwitchListener": {
|
||||
"enabled": false,
|
||||
"port": 8118,
|
||||
"password": "test"
|
||||
},
|
||||
|
||||
/* In a proxy configuration, you can setup ports that accept miners for work based on a
|
||||
specific algorithm instead of a specific coin. Miners that connect to these ports are
|
||||
/* With this switching configuration, you can setup ports that accept miners for work based on
|
||||
a specific algorithm instead of a specific coin. Miners that connect to these ports are
|
||||
automatically switched a coin determined by the server. The default coin is the first
|
||||
configured pool for each algorithm and coin switching can be triggered using the
|
||||
coinSwitch.js script in the scripts folder.
|
||||
cli.js script in the scripts folder.
|
||||
|
||||
Please note miner address authentication must be disabled when using NOMP in a proxy
|
||||
configuration and that payout processing is left up to the server administrator. */
|
||||
"proxy": {
|
||||
"sha256": {
|
||||
Miners connecting to these switching ports must use their public key in the format of
|
||||
RIPEMD160(SHA256(public-key)). An address for each type of coin is derived from the miner's
|
||||
public key, and payments are sent to that address. */
|
||||
"switching": {
|
||||
"switch1": {
|
||||
"enabled": false,
|
||||
"port": "3333",
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16, //Minimum difficulty
|
||||
"maxDiff": 512, //Network difficulty will be used if it is lower than this
|
||||
"targetTime": 15, //Try to get 1 share per this many seconds
|
||||
"retargetTime": 90, //Check to see if we should retarget every this many seconds
|
||||
"variancePercent": 30 //Allow time to very this % from target without retargeting
|
||||
"algorithm": "sha256",
|
||||
"ports": {
|
||||
"3333": {
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrypt": {
|
||||
"switch2": {
|
||||
"enabled": false,
|
||||
"port": "4444",
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16, //Minimum difficulty
|
||||
"maxDiff": 512, //Network difficulty will be used if it is lower than this
|
||||
"targetTime": 15, //Try to get 1 share per this many seconds
|
||||
"retargetTime": 90, //Check to see if we should retarget every this many seconds
|
||||
"variancePercent": 30 //Allow time to very this % from target without retargeting
|
||||
"algorithm": "scrypt",
|
||||
"ports": {
|
||||
"4444": {
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrypt-n": {
|
||||
"switch3": {
|
||||
"enabled": false,
|
||||
"port": "5555"
|
||||
"algorithm": "x11",
|
||||
"ports": {
|
||||
"5555": {
|
||||
"diff": 0.001
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"profitSwitch": {
|
||||
"enabled": false,
|
||||
"updateInterval": 600,
|
||||
"depth": 0.90,
|
||||
"usePoloniex": true,
|
||||
"useCryptsy": true,
|
||||
"useMintpal": true
|
||||
}
|
||||
}
|
||||
````
|
||||
|
@ -302,8 +313,8 @@ Description of options:
|
|||
job broadcast. */
|
||||
"txRefreshInterval": 20000,
|
||||
|
||||
/* Some miner software is bugged and will consider the pool offline if it doesn't receive
|
||||
anything for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast
|
||||
/* Some miner apps will consider the pool dead/offline if it doesn't receive anything new jobs
|
||||
for around a minute, so every time we broadcast jobs, set a timeout to rebroadcast
|
||||
in this many seconds unless we find a new job. Set to zero or remove to disable this. */
|
||||
"jobRebroadcastTimeout": 55,
|
||||
|
||||
|
@ -400,6 +411,10 @@ Description of options:
|
|||
"password": "mypass", //MySQL db password
|
||||
"database": "ltc", //MySQL db database name
|
||||
|
||||
/* Unregistered workers can automatically be registered (added to database) on stratum
|
||||
worker authentication if this is true. */
|
||||
"autoCreateWorker": false,
|
||||
|
||||
/* For when miner's authenticate: set to "password" for both worker name and password to
|
||||
be checked for in the database, set to "worker" for only work name to be checked, or
|
||||
don't use this option (set to "none") for no auth checks */
|
||||
|
@ -498,11 +513,11 @@ For more information on these configuration options see the [pool module documen
|
|||
1. In `config.json` set the port and password for `blockNotifyListener`
|
||||
2. In your daemon conf file set the `blocknotify` command to use:
|
||||
```
|
||||
node [path to scripts/blockNotify.js] [listener host]:[listener port] [listener password] [coin name in config] %s
|
||||
node [path to cli.js] [coin name in config] [block hash symbol]
|
||||
```
|
||||
Example: inside `dogecoin.conf` add the line
|
||||
```
|
||||
blocknotify=node scripts/blockNotify.js 127.0.0.1:8117 mySuperSecurePassword dogecoin %s
|
||||
blocknotify=node /home/nomp/scripts/cli.js blocknotify dogecoin %s
|
||||
```
|
||||
|
||||
Alternatively, you can use a more efficient block notify script written in pure C. Build and usage instructions
|
||||
|
@ -526,7 +541,8 @@ output from NOMP.
|
|||
|
||||
|
||||
#### Upgrading NOMP
|
||||
When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update the `node-statum-pool` module and any config files that may have been changed.
|
||||
When updating NOMP to the latest code its important to not only `git pull` the latest from this repo, but to also update
|
||||
the `node-statum-pool` and `node-multi-hashing` modules, and any config files that may have been changed.
|
||||
* Inside your NOMP directory (where the init.js script is) do `git pull` to get the latest NOMP code.
|
||||
* Remove the dependenices by deleting the `node_modules` directory with `rm -r node_modules`.
|
||||
* Run `npm update` to force updating/reinstalling of the dependencies.
|
||||
|
@ -549,7 +565,7 @@ Credits
|
|||
-------
|
||||
* [Jerry Brady / mintyfresh68](https://github.com/bluecircle) - got coin-switching fully working and developed proxy-per-algo feature
|
||||
* [Tony Dobbs](http://anthonydobbs.com) - designs for front-end and created the NOMP logo
|
||||
* [LucasJones(//github.com/LucasJones) - getting p2p block notify script working
|
||||
* [LucasJones](//github.com/LucasJones) - got p2p block notify working and implemented additional hashing algos
|
||||
* [vekexasia](//github.com/vekexasia) - co-developer & great tester
|
||||
* [TheSeven](//github.com/TheSeven) - answering an absurd amount of my questions and being a very helpful gentleman
|
||||
* [UdjinM6](//github.com/UdjinM6) - helped implement fee withdrawal in payment processing
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "ASICcoin",
|
||||
"symbol": "ASC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Battlecoin",
|
||||
"symbol": "BCX",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Benjamins",
|
||||
"symbol": "BEN",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Betacoin",
|
||||
"symbol": "BET",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Devcoin",
|
||||
"symbol": "DVC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "Ecoin",
|
||||
"symbol": "ECN",
|
||||
"algorithm": "keccak",
|
||||
"normalHashing": true,
|
||||
"diffShift": 32
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "eMark",
|
||||
"symbol": "DEM",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "FedoraCoin",
|
||||
"symbol": "TiPS",
|
||||
"algorithm": "scrypt"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Fireflycoin",
|
||||
"symbol": "FFC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Freicoin",
|
||||
"symbol": "FRC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Giarcoin",
|
||||
"symbol": "GIAR",
|
||||
"algorithm": "scrypt-n"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "GlobalBoost",
|
||||
"symbol": "BST",
|
||||
"algorithm": "scrypt"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "GlobalDenomination",
|
||||
"symbol": "GDN",
|
||||
"algorithm": "x11"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Guarantcoin",
|
||||
"symbol": "GTC",
|
||||
"algorithm": "scrypt-n"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Ixcoin",
|
||||
"symbol": "IXC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Joulecoin",
|
||||
"symbol": "XJO",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Mazacoin",
|
||||
"symbol": "MZC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Mintcoin",
|
||||
"symbol": "MINT",
|
||||
"algorithm": "scrypt"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "OpenSourcecoin",
|
||||
"symbol": "OSC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"name": "Quarkcoin",
|
||||
"symbol": "QRK",
|
||||
"algorithm": "quark"
|
||||
}
|
||||
"algorithm": "quark",
|
||||
"mposDiffMultiplier": 256
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Starcoin",
|
||||
"symbol": "STR",
|
||||
"algorithm": "scrypt"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Takcoin",
|
||||
"symbol": "TAK",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Teacoin",
|
||||
"symbol": "TEA",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Tekcoin",
|
||||
"symbol": "TEK",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "Tigercoin",
|
||||
"symbol": "TGC",
|
||||
"algorithm": "sha256"
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"logLevel": "debug",
|
||||
|
||||
"cliPort": 17117,
|
||||
|
||||
"clustering": {
|
||||
"enabled": true,
|
||||
"forks": "auto"
|
||||
|
@ -26,47 +28,47 @@
|
|||
"port": 6379
|
||||
},
|
||||
|
||||
"blockNotifyListener": {
|
||||
"enabled": false,
|
||||
"port": 8117,
|
||||
"password": "test"
|
||||
},
|
||||
|
||||
"coinSwitchListener": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8118,
|
||||
"password": "test"
|
||||
},
|
||||
|
||||
"proxy": {
|
||||
"sha256": {
|
||||
"switching": {
|
||||
"switch1": {
|
||||
"enabled": false,
|
||||
"port": "3333",
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
"algorithm": "sha256",
|
||||
"ports": {
|
||||
"3333": {
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrypt": {
|
||||
"switch2": {
|
||||
"enabled": false,
|
||||
"port": "4444",
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
"algorithm": "scrypt",
|
||||
"ports": {
|
||||
"4444": {
|
||||
"diff": 10,
|
||||
"varDiff": {
|
||||
"minDiff": 16,
|
||||
"maxDiff": 512,
|
||||
"targetTime": 15,
|
||||
"retargetTime": 90,
|
||||
"variancePercent": 30
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scrypt-n": {
|
||||
"switch3": {
|
||||
"enabled": false,
|
||||
"port": "5555"
|
||||
"algorithm": "x11",
|
||||
"ports": {
|
||||
"5555": {
|
||||
"diff": 0.001
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
219
init.js
219
init.js
|
@ -5,8 +5,7 @@ var cluster = require('cluster');
|
|||
|
||||
var async = require('async');
|
||||
var PoolLogger = require('./libs/logUtil.js');
|
||||
var BlocknotifyListener = require('./libs/blocknotifyListener.js');
|
||||
var CoinswitchListener = require('./libs/coinswitchListener.js');
|
||||
var CliListener = require('./libs/cliListener.js');
|
||||
var RedisBlocknotifyListener = require('./libs/redisblocknotifyListener.js');
|
||||
var PoolWorker = require('./libs/poolWorker.js');
|
||||
var PaymentProcessor = require('./libs/paymentProcessor.js');
|
||||
|
@ -23,6 +22,7 @@ if (!fs.existsSync('config.json')){
|
|||
}
|
||||
|
||||
var portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'})));
|
||||
var poolConfigs;
|
||||
|
||||
|
||||
var logger = new PoolLogger({
|
||||
|
@ -82,18 +82,68 @@ if (cluster.isWorker){
|
|||
var buildPoolConfigs = function(){
|
||||
var configs = {};
|
||||
var configDir = 'pool_configs/';
|
||||
|
||||
var poolConfigFiles = [];
|
||||
|
||||
|
||||
/* Get filenames of pool config json files that are enabled */
|
||||
fs.readdirSync(configDir).forEach(function(file){
|
||||
if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return;
|
||||
var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'})));
|
||||
if (!poolOptions.enabled) return;
|
||||
var coinFilePath = 'coins/' + poolOptions.coin;
|
||||
poolOptions.fileName = file;
|
||||
poolConfigFiles.push(poolOptions);
|
||||
});
|
||||
|
||||
|
||||
/* Ensure no pool uses any of the same ports as another pool */
|
||||
for (var i = 0; i < poolConfigFiles.length; i++){
|
||||
var ports = Object.keys(poolConfigFiles[i].ports);
|
||||
for (var f = 0; f < poolConfigFiles.length; f++){
|
||||
if (f === i) continue;
|
||||
var portsF = Object.keys(poolConfigFiles[f].ports);
|
||||
for (var g = 0; g < portsF.length; g++){
|
||||
if (ports.indexOf(portsF[g]) !== -1){
|
||||
logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){
|
||||
logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].fileName + ' pool');
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
poolConfigFiles.forEach(function(poolOptions){
|
||||
|
||||
poolOptions.coinFileName = poolOptions.coin;
|
||||
|
||||
var coinFilePath = 'coins/' + poolOptions.coinFileName;
|
||||
if (!fs.existsSync(coinFilePath)){
|
||||
logger.error('Master', poolOptions.coin, 'could not find file: ' + coinFilePath);
|
||||
logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'})));
|
||||
poolOptions.coin = coinProfile;
|
||||
|
||||
if (poolOptions.coin.name in configs){
|
||||
|
||||
logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName
|
||||
+ ' has same configured coin name ' + poolOptions.coin.name + ' as coins/'
|
||||
+ configs[poolOptions.coin.name].coinFileName + ' used by pool config '
|
||||
+ configs[poolOptions.coin.name].fileName);
|
||||
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
configs[poolOptions.coin.name] = poolOptions;
|
||||
|
||||
if (!(coinProfile.algorithm in algos)){
|
||||
|
@ -107,7 +157,7 @@ var buildPoolConfigs = function(){
|
|||
|
||||
|
||||
|
||||
var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
||||
var spawnPoolWorkers = function(){
|
||||
|
||||
Object.keys(poolConfigs).forEach(function(coin){
|
||||
var p = poolConfigs[coin];
|
||||
|
@ -130,6 +180,7 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
var serializedConfigs = JSON.stringify(poolConfigs);
|
||||
|
||||
var numForks = (function(){
|
||||
|
@ -185,51 +236,111 @@ var spawnPoolWorkers = function(portalConfig, poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startBlockListener = function(portalConfig){
|
||||
//block notify options
|
||||
//setup block notify here and use IPC to tell appropriate pools
|
||||
var listener = new BlocknotifyListener(portalConfig.blockNotifyListener);
|
||||
var startCliListener = function(){
|
||||
|
||||
var cliPort = portalConfig.cliPort;
|
||||
|
||||
var listener = new CliListener(cliPort);
|
||||
listener.on('log', function(text){
|
||||
logger.debug('Master', 'Blocknotify', text);
|
||||
});
|
||||
listener.on('hash', function(message){
|
||||
logger.debug('Master', 'CLI', text);
|
||||
}).on('command', function(command, params, options, reply){
|
||||
|
||||
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash};
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send(ipcMessage);
|
||||
});
|
||||
|
||||
});
|
||||
listener.start();
|
||||
switch(command){
|
||||
case 'blocknotify':
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]});
|
||||
});
|
||||
reply('Pool workers notified');
|
||||
break;
|
||||
case 'coinswitch':
|
||||
processCoinSwitchCommand(params, options, reply);
|
||||
break;
|
||||
case 'reloadpool':
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send({type: 'reloadpool', coin: params[0] });
|
||||
});
|
||||
reply('reloaded pool ' + params[0]);
|
||||
break;
|
||||
default:
|
||||
reply('unrecognized command "' + command + '"');
|
||||
break;
|
||||
}
|
||||
}).start();
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Receives authenticated events from coin switch listener and triggers proxy
|
||||
// to swtich to a new coin.
|
||||
//
|
||||
var startCoinswitchListener = function(portalConfig){
|
||||
var listener = new CoinswitchListener(portalConfig.coinSwitchListener);
|
||||
listener.on('log', function(text){
|
||||
logger.debug('Master', 'Coinswitch', text);
|
||||
});
|
||||
listener.on('switchcoin', function(message){
|
||||
var ipcMessage = {type:'blocknotify', coin: message.coin, hash: message.hash};
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send(ipcMessage);
|
||||
});
|
||||
var ipcMessage = {
|
||||
type:'switch',
|
||||
coin: message.coin
|
||||
};
|
||||
Object.keys(cluster.workers).forEach(function(id) {
|
||||
cluster.workers[id].send(ipcMessage);
|
||||
var processCoinSwitchCommand = function(params, options, reply){
|
||||
|
||||
var logSystem = 'CLI';
|
||||
var logComponent = 'coinswitch';
|
||||
|
||||
var replyError = function(msg){
|
||||
reply(msg);
|
||||
logger.error(logSystem, logComponent, msg);
|
||||
};
|
||||
|
||||
if (!params[0]) {
|
||||
replyError('Coin name required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params[1] && !options.algorithm){
|
||||
replyError('If switch key is not provided then algorithm options must be specified');
|
||||
return;
|
||||
}
|
||||
else if (params[1] && !portalConfig.switching[params[1]]){
|
||||
replyError('Switch key not recognized: ' + params[1]);
|
||||
return;
|
||||
}
|
||||
else if (options.algorithm && !Object.keys(portalConfig.switching).filter(function(s){
|
||||
return portalConfig.switching[s].algorithm === options.algorithm;
|
||||
})[0]){
|
||||
replyError('No switching options contain the algorithm ' + options.algorithm);
|
||||
return;
|
||||
}
|
||||
|
||||
var messageCoin = params[0].toLowerCase();
|
||||
var newCoin = Object.keys(poolConfigs).filter(function(p){
|
||||
return p.toLowerCase() === messageCoin;
|
||||
})[0];
|
||||
|
||||
if (!newCoin){
|
||||
replyError('Switch message to coin that is not recognized: ' + messageCoin);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var switchNames = [];
|
||||
|
||||
if (params[1]) {
|
||||
switchNames.push(params[1]);
|
||||
}
|
||||
else{
|
||||
for (var name in portalConfig.switching){
|
||||
if (portalConfig.switching[name].enabled && portalConfig.switching[name].algorithm === options.algorithm)
|
||||
switchNames.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
switchNames.forEach(function(name){
|
||||
if (poolConfigs[newCoin].coin.algorithm !== portalConfig.switching[name].algorithm){
|
||||
replyError('Cannot switch a '
|
||||
+ portalConfig.switching[name].algorithm
|
||||
+ ' algo pool to coin ' + newCoin + ' with ' + poolConfigs[newCoin].coin.algorithm + ' algo');
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(cluster.workers).forEach(function (id) {
|
||||
cluster.workers[id].send({type: 'coinswitch', coin: newCoin, switchName: name });
|
||||
});
|
||||
});
|
||||
listener.start();
|
||||
|
||||
reply('Switch message sent to pool workers');
|
||||
|
||||
};
|
||||
|
||||
var startRedisBlockListener = function(portalConfig){
|
||||
|
||||
var startRedisBlockListener = function(){
|
||||
//block notify options
|
||||
//setup block notify here and use IPC to tell appropriate pools
|
||||
|
||||
|
@ -248,7 +359,7 @@ var startRedisBlockListener = function(portalConfig){
|
|||
};
|
||||
|
||||
|
||||
var startPaymentProcessor = function(poolConfigs){
|
||||
var startPaymentProcessor = function(){
|
||||
|
||||
var enabledForAny = false;
|
||||
for (var pool in poolConfigs){
|
||||
|
@ -276,7 +387,7 @@ var startPaymentProcessor = function(poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startWebsite = function(portalConfig, poolConfigs){
|
||||
var startWebsite = function(){
|
||||
|
||||
if (!portalConfig.website.enabled) return;
|
||||
|
||||
|
@ -294,10 +405,10 @@ var startWebsite = function(portalConfig, poolConfigs){
|
|||
};
|
||||
|
||||
|
||||
var startProfitSwitch = function(portalConfig, poolConfigs){
|
||||
var startProfitSwitch = function(){
|
||||
|
||||
if (!portalConfig.profitSwitch.enabled){
|
||||
logger.error('Master', 'Profit', 'Profit auto switching disabled');
|
||||
if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){
|
||||
//logger.error('Master', 'Profit', 'Profit auto switching disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -318,20 +429,18 @@ var startProfitSwitch = function(portalConfig, poolConfigs){
|
|||
|
||||
(function init(){
|
||||
|
||||
var poolConfigs = buildPoolConfigs();
|
||||
poolConfigs = buildPoolConfigs();
|
||||
|
||||
spawnPoolWorkers(portalConfig, poolConfigs);
|
||||
spawnPoolWorkers();
|
||||
|
||||
startPaymentProcessor(poolConfigs);
|
||||
startPaymentProcessor();
|
||||
|
||||
startBlockListener(portalConfig);
|
||||
startRedisBlockListener();
|
||||
|
||||
startCoinswitchListener(portalConfig);
|
||||
startWebsite();
|
||||
|
||||
startRedisBlockListener(portalConfig);
|
||||
startProfitSwitch();
|
||||
|
||||
startWebsite(portalConfig, poolConfigs);
|
||||
|
||||
startProfitSwitch(portalConfig, poolConfigs);
|
||||
startCliListener();
|
||||
|
||||
})();
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -7,38 +7,26 @@ module.exports = function(logger, poolConfig){
|
|||
|
||||
var connection;
|
||||
|
||||
|
||||
var logIdentify = 'MySQL';
|
||||
var logComponent = coin;
|
||||
|
||||
function connect(){
|
||||
connection = mysql.createConnection({
|
||||
|
||||
connection = mysql.createPool({
|
||||
host: mposConfig.host,
|
||||
port: mposConfig.port,
|
||||
user: mposConfig.user,
|
||||
password: mposConfig.password,
|
||||
database: mposConfig.database
|
||||
});
|
||||
connection.connect(function(err){
|
||||
if (err)
|
||||
logger.error(logIdentify, logComponent, 'Could not connect to mysql database: ' + JSON.stringify(err))
|
||||
else{
|
||||
logger.debug(logIdentify, logComponent, 'Successful connection to MySQL database');
|
||||
}
|
||||
});
|
||||
connection.on('error', function(err){
|
||||
if(err.code === 'PROTOCOL_CONNECTION_LOST') {
|
||||
logger.warning(logIdentify, logComponent, 'Lost connection to MySQL database, attempting reconnection...');
|
||||
connect();
|
||||
}
|
||||
else{
|
||||
logger.error(logIdentify, logComponent, 'Database error: ' + JSON.stringify(err))
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
connect();
|
||||
|
||||
this.handleAuth = function(workerName, password, authCallback){
|
||||
|
||||
|
||||
connection.query(
|
||||
'SELECT password FROM pool_worker WHERE username = LOWER(?)',
|
||||
[workerName.toLowerCase()],
|
||||
|
@ -48,8 +36,39 @@ module.exports = function(logger, poolConfig){
|
|||
JSON.stringify(err));
|
||||
authCallback(false);
|
||||
}
|
||||
else if (!result[0])
|
||||
authCallback(false);
|
||||
else if (!result[0]){
|
||||
if(mposConfig.autoCreateWorker){
|
||||
var account = workerName.split('.')[0];
|
||||
connection.query(
|
||||
'SELECT id,username FROM accounts WHERE username = LOWER(?)',
|
||||
[account.toLowerCase()],
|
||||
function(err, result){
|
||||
if (err){
|
||||
logger.error(logIdentify, logComponent, 'Database error when authenticating account: ' +
|
||||
JSON.stringify(err));
|
||||
authCallback(false);
|
||||
}else if(!result[0]){
|
||||
authCallback(false);
|
||||
}else{
|
||||
connection.query(
|
||||
"INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);",
|
||||
[result[0].id,workerName.toLowerCase(),password],
|
||||
function(err, result){
|
||||
if (err){
|
||||
logger.error(logIdentify, logComponent, 'Database error when insert worker: ' +
|
||||
JSON.stringify(err));
|
||||
authCallback(false);
|
||||
}else {
|
||||
authCallback(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
}else{
|
||||
authCallback(false);
|
||||
}
|
||||
}
|
||||
else if (mposConfig.stratumAuth === 'worker')
|
||||
authCallback(true);
|
||||
else if (result[0].password === password)
|
||||
|
|
|
@ -2,7 +2,7 @@ var redis = require('redis');
|
|||
var async = require('async');
|
||||
|
||||
var Stratum = require('stratum-pool');
|
||||
|
||||
var util = require('stratum-pool/lib/util.js');
|
||||
|
||||
|
||||
module.exports = function(logger){
|
||||
|
@ -38,8 +38,6 @@ module.exports = function(logger){
|
|||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -216,6 +214,8 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
return ['gettransaction', [r.txHash]];
|
||||
});
|
||||
|
||||
batchRPCcommand.push(['getaccount', [poolOptions.address]]);
|
||||
|
||||
daemon.batchCmd(batchRPCcommand, function(error, txDetails){
|
||||
|
||||
if (error || !txDetails){
|
||||
|
@ -224,7 +224,15 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
return;
|
||||
}
|
||||
|
||||
var addressAccount;
|
||||
|
||||
txDetails.forEach(function(tx, i){
|
||||
|
||||
if (i === txDetails.length - 1){
|
||||
addressAccount = tx.result;
|
||||
return;
|
||||
}
|
||||
|
||||
var round = rounds[i];
|
||||
|
||||
if (tx.error && tx.error.code === -5 || round.blockHash !== tx.result.blockhash){
|
||||
|
@ -273,7 +281,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
rpc.gettransaction.amount tells us how much we get in whole coin units. Therefore,
|
||||
we simply divide the two to get the magnitude. I don't know math, there is probably
|
||||
a better term than 'magnitude'. Sue me or do a pull request to fix it. */
|
||||
var roundMagnitude = r.reward / r.amount;
|
||||
var roundMagnitude = Math.round(r.reward / r.amount);
|
||||
|
||||
if (!magnitude) {
|
||||
magnitude = roundMagnitude;
|
||||
|
@ -303,7 +311,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
callback('Check finished - no confirmed or orphaned blocks found');
|
||||
}
|
||||
else{
|
||||
callback(null, rounds, magnitude);
|
||||
callback(null, rounds, magnitude, addressAccount);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -311,7 +319,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
|
||||
/* Does a batch redis call to get shares contributed to each round. Then calculates the reward
|
||||
amount owned to each miner for each round. */
|
||||
function(rounds, magnitude, callback){
|
||||
function(rounds, magnitude, addressAccount, callback){
|
||||
|
||||
|
||||
var shareLookups = rounds.map(function(r){
|
||||
|
@ -356,11 +364,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
var reward = round.reward * (1 - processingConfig.feePercent);
|
||||
|
||||
var totalShares = Object.keys(workerShares).reduce(function(p, c){
|
||||
return p + parseInt(workerShares[c])
|
||||
return p + parseFloat(workerShares[c])
|
||||
}, 0);
|
||||
|
||||
for (var worker in workerShares){
|
||||
var percent = parseInt(workerShares[worker]) / totalShares;
|
||||
var percent = parseFloat(workerShares[worker]) / totalShares;
|
||||
var workerRewardTotal = Math.floor(reward * percent);
|
||||
if (!(worker in workerRewards)) workerRewards[worker] = 0;
|
||||
workerRewards[worker] += workerRewardTotal;
|
||||
|
@ -370,13 +378,13 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
|
||||
});
|
||||
|
||||
callback(null, rounds, magnitude, workerRewards, orphanMergeCommands);
|
||||
callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/* Does a batch call to redis to get worker existing balances from coin_balances*/
|
||||
function(rounds, magnitude, workerRewards, orphanMergeCommands, callback){
|
||||
function(rounds, magnitude, workerRewards, orphanMergeCommands, addressAccount, callback){
|
||||
|
||||
var workers = Object.keys(workerRewards);
|
||||
|
||||
|
@ -394,7 +402,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
}
|
||||
|
||||
|
||||
callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances);
|
||||
callback(null, rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount);
|
||||
});
|
||||
|
||||
},
|
||||
|
@ -406,12 +414,11 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
when deciding the sent balance, it the difference should be -1*amount they had in db,
|
||||
if not sending the balance, the differnce should be +(the amount they earned this round)
|
||||
*/
|
||||
function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, callback){
|
||||
function(rounds, magnitude, workerRewards, orphanMergeCommands, workerBalances, addressAccount, callback){
|
||||
|
||||
//number of satoshis in a single coin unit - this can be different for coins so we calculate it :)
|
||||
|
||||
|
||||
daemon.cmd('getbalance', [''], function(results){
|
||||
daemon.cmd('getbalance', [addressAccount || ''], function(results){
|
||||
|
||||
var totalBalance = results[0].response * magnitude;
|
||||
var toBePaid = 0;
|
||||
|
@ -474,9 +481,9 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
/* TODO: Need to convert all these variables into whole coin units before displaying because
|
||||
humans aren't good at reading satoshi units. */
|
||||
callback('Check finished - payments would wipe out minimum reserve, tried to pay out ' +
|
||||
toBePaid + ' and collect ' + feeAmountToBeCollected + ' as fees' +
|
||||
' but only have ' + totalBalance + '. Left over balance would be ' + balanceLeftOver +
|
||||
', needs to be at least ' + minReserveSatoshis);
|
||||
(toBePaid/magnitude) + ' and collect ' + (feeAmountToBeCollected/magnitude) + ' as fees' +
|
||||
' but only have ' + (totalBalance/magnitude) + '. Left over balance would be ' + (balanceLeftOver/magnitude) +
|
||||
', needs to be at least ' + (minReserveSatoshis/magnitude));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -495,7 +502,8 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
}
|
||||
})();
|
||||
movePendingCommands.push(['smove', coin + '_blocksPending', coin + destinationSet, r.serialized]);
|
||||
roundsToDelete.push(coin + '_shares:round' + r.height)
|
||||
if (r.category === 'generate')
|
||||
roundsToDelete.push(coin + '_shares:round' + r.height)
|
||||
});
|
||||
|
||||
var finalRedisCommands = [];
|
||||
|
@ -522,23 +530,23 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
|
||||
finalRedisCommands.push(['bgsave']);
|
||||
|
||||
callback(null, magnitude, workerPayments, finalRedisCommands);
|
||||
callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount);
|
||||
|
||||
});
|
||||
},
|
||||
|
||||
function(magnitude, workerPayments, finalRedisCommands, callback) {
|
||||
function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback) {
|
||||
/* Save final redis cleanout commands in case something goes wrong during payments */
|
||||
redisClient.set(coin + '_finalRedisCommands', JSON.stringify(finalRedisCommands), function(error, reply) {
|
||||
if (error){
|
||||
callback('Check finished - error with saving finalRedisCommands' + JSON.stringify(error));
|
||||
return;
|
||||
}
|
||||
callback(null, magnitude, workerPayments, finalRedisCommands);
|
||||
callback(null, magnitude, workerPayments, finalRedisCommands, addressAccount);
|
||||
});
|
||||
},
|
||||
|
||||
function(magnitude, workerPayments, finalRedisCommands, callback){
|
||||
function(magnitude, workerPayments, finalRedisCommands, addressAccount, callback){
|
||||
|
||||
//This does the final all-or-nothing atom transaction if block deamon sent payments
|
||||
var finalizeRedisTx = function(){
|
||||
|
@ -563,14 +571,19 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
var totalAmountUnits = 0;
|
||||
for (var address in workerPayments){
|
||||
var coinUnits = toPrecision(workerPayments[address] / magnitude, coinPrecision);
|
||||
addressAmounts[address] = coinUnits;
|
||||
var properAddress = getProperAddress(address);
|
||||
if (!properAddress){
|
||||
logger.error(logSystem, logComponent, 'Could not convert pubkey ' + address + ' into address');
|
||||
continue;
|
||||
}
|
||||
addressAmounts[properAddress] = coinUnits;
|
||||
totalAmountUnits += coinUnits;
|
||||
}
|
||||
|
||||
logger.debug(logSystem, logComponent, 'Payments to be sent to: ' + JSON.stringify(addressAmounts));
|
||||
|
||||
processingPayments = true;
|
||||
daemon.cmd('sendmany', ['', addressAmounts], function(results){
|
||||
daemon.cmd('sendmany', [addressAccount || '', addressAmounts], function(results){
|
||||
|
||||
if (results[0].error){
|
||||
callback('Check finished - error with sendmany ' + JSON.stringify(results[0].error));
|
||||
|
@ -591,7 +604,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
}
|
||||
var feeAmountUnits = parseFloat((totalAmountUnits / (1 - processingConfig.feePercent) * processingConfig.feePercent).toFixed(coinPrecision));
|
||||
var poolFees = feeAmountUnits - results[0].response.fee;
|
||||
daemon.cmd('move', ['', processingConfig.feeCollectAccount, poolFees], function(results){
|
||||
daemon.cmd('move', [addressAccount || '', processingConfig.feeCollectAccount, poolFees], function(results){
|
||||
if (results[0].error){
|
||||
callback('Check finished - error with move ' + JSON.stringify(results[0].error));
|
||||
return;
|
||||
|
@ -619,6 +632,13 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
};
|
||||
|
||||
|
||||
var getProperAddress = function(address){
|
||||
if (address.length === 40){
|
||||
return util.addressFromEx(poolOptions.address, address);
|
||||
}
|
||||
else return address;
|
||||
};
|
||||
|
||||
var withdrawalProfit = function(){
|
||||
|
||||
if (!processingConfig.feeWithdrawalThreshold) return;
|
||||
|
@ -641,7 +661,7 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
|
||||
daemon.cmd('sendmany', [processingConfig.feeCollectAccount, withdrawal], function(results){
|
||||
if (results[0].error){
|
||||
logger.debug(logSystem, logComponent, 'Profit withdrawal finished - error with sendmany '
|
||||
logger.debug(logSystem, logComponent, 'Profit withdrawal of ' + withdrawalAmount + ' failed - error with sendmany '
|
||||
+ JSON.stringify(results[0].error));
|
||||
return;
|
||||
}
|
||||
|
@ -652,5 +672,4 @@ function SetupForPool(logger, poolOptions, setupFinished){
|
|||
});
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
}
|
|
@ -18,6 +18,8 @@ module.exports = function(logger){
|
|||
|
||||
var proxySwitch = {};
|
||||
|
||||
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host);
|
||||
|
||||
//Handle messages from master process sent via IPC
|
||||
process.on('message', function(message) {
|
||||
switch(message.type){
|
||||
|
@ -42,26 +44,21 @@ module.exports = function(logger){
|
|||
break;
|
||||
|
||||
// IPC message for pool switching
|
||||
case 'switch':
|
||||
case 'coinswitch':
|
||||
var logSystem = 'Proxy';
|
||||
var logComponent = 'Switch';
|
||||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||
|
||||
var messageCoin = message.coin.toLowerCase();
|
||||
var newCoin = Object.keys(pools).filter(function(p){
|
||||
return p.toLowerCase() === messageCoin;
|
||||
})[0];
|
||||
var switchName = message.switchName;
|
||||
|
||||
if (!newCoin){
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Switch message to coin that is not recognized: ' + messageCoin);
|
||||
break;
|
||||
}
|
||||
var newCoin = message.coin;
|
||||
|
||||
var algo = poolConfigs[newCoin].coin.algorithm;
|
||||
|
||||
var newPool = pools[newCoin];
|
||||
var oldCoin = proxySwitch[algo].currentPool;
|
||||
var oldCoin = proxySwitch[switchName].currentPool;
|
||||
var oldPool = pools[oldCoin];
|
||||
var proxyPort = proxySwitch[algo].port;
|
||||
var proxyPorts = Object.keys(proxySwitch[switchName].ports);
|
||||
|
||||
if (newCoin == oldCoin) {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin);
|
||||
|
@ -74,25 +71,23 @@ module.exports = function(logger){
|
|||
oldPool.relinquishMiners(
|
||||
function (miner, cback) {
|
||||
// relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there.
|
||||
cback(miner.client.socket.localPort == proxyPort)
|
||||
cback(proxyPorts.indexOf(miner.client.socket.localPort.toString()) !== -1)
|
||||
},
|
||||
function (clients) {
|
||||
newPool.attachMiners(clients);
|
||||
}
|
||||
);
|
||||
proxySwitch[algo].currentPool = newCoin;
|
||||
proxySwitch[switchName].currentPool = newCoin;
|
||||
|
||||
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host)
|
||||
redisClient.on('ready', function(){
|
||||
redisClient.hset('proxyState', algo, newCoin, function(error, obj) {
|
||||
if (error) {
|
||||
logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err))
|
||||
}
|
||||
else {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo);
|
||||
}
|
||||
});
|
||||
redisClient.hset('proxyState', algo, newCoin, function(error, obj) {
|
||||
if (error) {
|
||||
logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err))
|
||||
}
|
||||
else {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -117,9 +112,9 @@ module.exports = function(logger){
|
|||
|
||||
//Functions required for MPOS compatibility
|
||||
if (shareProcessing && shareProcessing.mpos && shareProcessing.mpos.enabled){
|
||||
var mposCompat = new MposCompatibility(logger, poolOptions)
|
||||
var mposCompat = new MposCompatibility(logger, poolOptions);
|
||||
|
||||
handlers.auth = function(workerName, password, authCallback){
|
||||
handlers.auth = function(port, workerName, password, authCallback){
|
||||
mposCompat.handleAuth(workerName, password, authCallback);
|
||||
};
|
||||
|
||||
|
@ -135,12 +130,32 @@ module.exports = function(logger){
|
|||
//Functions required for internal payment processing
|
||||
else if (shareProcessing && shareProcessing.internal && shareProcessing.internal.enabled){
|
||||
|
||||
var shareProcessor = new ShareProcessor(logger, poolOptions)
|
||||
var shareProcessor = new ShareProcessor(logger, poolOptions);
|
||||
|
||||
handlers.auth = function(workerName, password, authCallback){
|
||||
handlers.auth = function(port, workerName, password, authCallback){
|
||||
if (shareProcessing.internal.validateWorkerAddress !== true)
|
||||
authCallback(true);
|
||||
else {
|
||||
port = port.toString();
|
||||
if (portalConfig.switching) {
|
||||
for (var switchName in portalConfig.switching) {
|
||||
if (portalConfig.switching[switchName].enabled && Object.keys(portalConfig.switching[switchName].ports).indexOf(port) !== -1) {
|
||||
if (workerName.length === 40) {
|
||||
try {
|
||||
new Buffer(workerName, 'hex');
|
||||
authCallback(true);
|
||||
}
|
||||
catch (e) {
|
||||
authCallback(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
authCallback(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pool.daemon.cmd('validateaddress', [workerName], function(results){
|
||||
var isValid = results.filter(function(r){return r.response.isvalid}).length > 0;
|
||||
authCallback(isValid);
|
||||
|
@ -153,8 +168,8 @@ module.exports = function(logger){
|
|||
};
|
||||
}
|
||||
|
||||
var authorizeFN = function (ip, workerName, password, callback) {
|
||||
handlers.auth(workerName, password, function(authorized){
|
||||
var authorizeFN = function (ip, port, workerName, password, callback) {
|
||||
handlers.auth(port, workerName, password, function(authorized){
|
||||
|
||||
var authString = authorized ? 'Authorized' : 'Unauthorized ';
|
||||
|
||||
|
@ -202,9 +217,9 @@ module.exports = function(logger){
|
|||
});
|
||||
|
||||
|
||||
if (typeof(portalConfig.proxy) !== 'undefined') {
|
||||
if (portalConfig.switching) {
|
||||
|
||||
var logSystem = 'Proxy';
|
||||
var logSystem = 'Switching';
|
||||
var logComponent = 'Setup';
|
||||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||
|
||||
|
@ -215,73 +230,93 @@ module.exports = function(logger){
|
|||
// on the last pool it was using when reloaded or restarted
|
||||
//
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis');
|
||||
var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host)
|
||||
redisClient.on('ready', function(){
|
||||
redisClient.hgetall("proxyState", function(error, obj) {
|
||||
if (error || obj == null) {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis');
|
||||
}
|
||||
else {
|
||||
proxyState = obj;
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis');
|
||||
}
|
||||
|
||||
//
|
||||
// Setup proxySwitch object to control proxy operations from configuration and any restored
|
||||
// state. Each algorithm has a listening port, current coin name, and an active pool to
|
||||
// which traffic is directed when activated in the config.
|
||||
//
|
||||
// In addition, the proxy config also takes diff and varDiff parmeters the override the
|
||||
// defaults for the standard config of the coin.
|
||||
//
|
||||
Object.keys(portalConfig.proxy).forEach(function(algorithm) {
|
||||
|
||||
if (portalConfig.proxy[algorithm].enabled === true) {
|
||||
var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm);
|
||||
proxySwitch[algorithm] = {
|
||||
port: portalConfig.proxy[algorithm].port,
|
||||
currentPool: initalPool,
|
||||
proxy: {}
|
||||
};
|
||||
|
||||
|
||||
// Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up
|
||||
//
|
||||
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was
|
||||
// routed into instead.
|
||||
//
|
||||
if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) {
|
||||
proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff);
|
||||
proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff;
|
||||
}
|
||||
Object.keys(pools).forEach(function (coinName) {
|
||||
var a = poolConfigs[coinName].coin.algorithm;
|
||||
var p = pools[coinName];
|
||||
if (a === algorithm) {
|
||||
p.setVarDiff(proxySwitch[algorithm].port, proxySwitch[algorithm].varDiff);
|
||||
/*redisClient.on('error', function(err){
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err);
|
||||
});*/
|
||||
|
||||
redisClient.hgetall("proxyState", function(error, obj) {
|
||||
if (error || obj == null) {
|
||||
//logger.debug(logSystem, logComponent, logSubCat, 'No last proxy state found in redis');
|
||||
}
|
||||
else {
|
||||
proxyState = obj;
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis');
|
||||
}
|
||||
|
||||
//
|
||||
// Setup proxySwitch object to control proxy operations from configuration and any restored
|
||||
// state. Each algorithm has a listening port, current coin name, and an active pool to
|
||||
// which traffic is directed when activated in the config.
|
||||
//
|
||||
// In addition, the proxy config also takes diff and varDiff parmeters the override the
|
||||
// defaults for the standard config of the coin.
|
||||
//
|
||||
Object.keys(portalConfig.switching).forEach(function(switchName) {
|
||||
|
||||
var algorithm = portalConfig.switching[switchName].algorithm;
|
||||
|
||||
if (portalConfig.switching[switchName].enabled === true) {
|
||||
var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm);
|
||||
proxySwitch[switchName] = {
|
||||
algorithm: algorithm,
|
||||
ports: portalConfig.switching[switchName].ports,
|
||||
currentPool: initalPool,
|
||||
servers: []
|
||||
};
|
||||
|
||||
|
||||
// Copy diff and vardiff configuation into pools that match our algorithm so the stratum server can pick them up
|
||||
//
|
||||
// Note: This seems a bit wonky and brittle - better if proxy just used the diff config of the port it was
|
||||
// routed into instead.
|
||||
//
|
||||
/*if (portalConfig.proxy[algorithm].hasOwnProperty('varDiff')) {
|
||||
proxySwitch[algorithm].varDiff = new Stratum.varDiff(proxySwitch[algorithm].port, portalConfig.proxy[algorithm].varDiff);
|
||||
proxySwitch[algorithm].diff = portalConfig.proxy[algorithm].diff;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
Object.keys(pools).forEach(function (coinName) {
|
||||
var p = pools[coinName];
|
||||
if (poolConfigs[coinName].coin.algorithm === algorithm) {
|
||||
for (var port in portalConfig.switching[switchName].ports) {
|
||||
if (portalConfig.switching[switchName].ports[port].varDiff)
|
||||
p.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
proxySwitch[algorithm].proxy = net.createServer(function(socket) {
|
||||
var currentPool = proxySwitch[algorithm].currentPool;
|
||||
var logSubCat = 'Thread ' + (parseInt(forkId) + 1);
|
||||
|
||||
logger.debug(logSystem, 'Connect', logSubCat, 'Proxy connect from ' + socket.remoteAddress + ' on ' + proxySwitch[algorithm].port
|
||||
+ ' routing to ' + currentPool);
|
||||
Object.keys(proxySwitch[switchName].ports).forEach(function(port){
|
||||
var f = net.createServer(function(socket) {
|
||||
var currentPool = proxySwitch[switchName].currentPool;
|
||||
|
||||
logger.debug(logSystem, 'Connect', logSubCat, 'Connection to '
|
||||
+ switchName + ' from '
|
||||
+ socket.remoteAddress + ' on '
|
||||
+ port + ' routing to ' + currentPool);
|
||||
|
||||
pools[currentPool].getStratumServer().handleNewClient(socket);
|
||||
|
||||
}).listen(parseInt(proxySwitch[algorithm].port), function() {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Proxy listening for ' + algorithm + ' on port ' + proxySwitch[algorithm].port
|
||||
+ ' into ' + proxySwitch[algorithm].currentPool);
|
||||
}).listen(parseInt(port), function() {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName
|
||||
+ '" listening for ' + algorithm
|
||||
+ ' on port ' + port
|
||||
+ ' into ' + proxySwitch[switchName].currentPool);
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.');
|
||||
}
|
||||
});
|
||||
proxySwitch[switchName].servers.push(f);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
else {
|
||||
//logger.debug(logSystem, logComponent, logSubCat, 'Proxy pool for ' + algorithm + ' disabled.');
|
||||
}
|
||||
});
|
||||
}).on('error', function(err){
|
||||
logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -420,31 +420,28 @@ module.exports = function(logger){
|
|||
};
|
||||
this.getDaemonInfoForCoin = function(symbol, cfg, callback){
|
||||
var daemon = new Stratum.daemon.interface([cfg]);
|
||||
daemon.once('online', function(){
|
||||
daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){
|
||||
if (result[0].error != null){
|
||||
logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0]));
|
||||
callback(null); // fail gracefully for each coin
|
||||
return;
|
||||
}
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
var response = result[0].response;
|
||||
|
||||
// some shitcoins dont provide target, only bits, so we need to deal with both
|
||||
var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits);
|
||||
coinStatus.difficulty = parseFloat((diff1.toNumber() / target.toNumber()).toFixed(9));
|
||||
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
|
||||
|
||||
coinStatus.reward = new Number(response.coinbasevalue / 100000000);
|
||||
callback(null);
|
||||
});
|
||||
}).once('connectionFailed', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).on('error', function(error){
|
||||
daemon.on('error', function(error){
|
||||
logger.error(logSystem, symbol, JSON.stringify(error));
|
||||
callback(null); // fail gracefully for each coin
|
||||
}).init();
|
||||
|
||||
daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result) {
|
||||
if (result[0].error != null) {
|
||||
logger.error(logSystem, symbol, 'Error while reading daemon info: ' + JSON.stringify(result[0]));
|
||||
callback(null); // fail gracefully for each coin
|
||||
return;
|
||||
}
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
var response = result[0].response;
|
||||
|
||||
// some shitcoins dont provide target, only bits, so we need to deal with both
|
||||
var target = response.target ? bignum(response.target, 16) : util.bignumFromBitsHex(response.bits);
|
||||
coinStatus.difficulty = parseFloat((diff1 / target.toNumber()).toFixed(9));
|
||||
logger.debug(logSystem, symbol, 'difficulty is ' + coinStatus.difficulty);
|
||||
|
||||
coinStatus.reward = response.coinbasevalue / 100000000;
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -453,8 +450,8 @@ module.exports = function(logger){
|
|||
Object.keys(profitStatus).forEach(function(algo){
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol){
|
||||
var coinStatus = profitStatus[symbolToAlgorithmMap[symbol]][symbol];
|
||||
coinStatus.blocksPerMhPerHour = new Number(86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000)));
|
||||
coinStatus.coinsPerMhPerHour = new Number(coinStatus.reward * coinStatus.blocksPerMhPerHour);
|
||||
coinStatus.blocksPerMhPerHour = 86400 / ((coinStatus.difficulty * Math.pow(2,32)) / (1 * 1000 * 1000));
|
||||
coinStatus.coinsPerMhPerHour = coinStatus.reward * coinStatus.blocksPerMhPerHour;
|
||||
});
|
||||
});
|
||||
callback(null);
|
||||
|
@ -467,7 +464,7 @@ module.exports = function(logger){
|
|||
|
||||
var bestExchange;
|
||||
var bestCoin;
|
||||
var bestBtcPerMhPerHour = new Number(0);
|
||||
var bestBtcPerMhPerHour = 0;
|
||||
|
||||
Object.keys(profitStatus[algo]).forEach(function(symbol) {
|
||||
var coinStatus = profitStatus[algo][symbol];
|
||||
|
@ -475,7 +472,7 @@ module.exports = function(logger){
|
|||
Object.keys(coinStatus.exchangeInfo).forEach(function(exchange){
|
||||
var exchangeData = coinStatus.exchangeInfo[exchange];
|
||||
if (exchangeData.hasOwnProperty('BTC') && exchangeData['BTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number(exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour);
|
||||
var btcPerMhPerHour = exchangeData['BTC'].weightedBid * coinStatus.coinsPerMhPerHour;
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
|
@ -485,7 +482,7 @@ module.exports = function(logger){
|
|||
logger.debug(logSystem, 'CALC', 'BTC/' + symbol + ' on ' + exchange + ' with ' + coinStatus.btcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
}
|
||||
if (exchangeData.hasOwnProperty('LTC') && exchangeData['LTC'].hasOwnProperty('weightedBid')){
|
||||
var btcPerMhPerHour = new Number((exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc);
|
||||
var btcPerMhPerHour = (exchangeData['LTC'].weightedBid * coinStatus.coinsPerMhPerHour) * exchangeData['LTC'].ltcToBtc;
|
||||
if (btcPerMhPerHour > bestBtcPerMhPerHour){
|
||||
bestBtcPerMhPerHour = btcPerMhPerHour;
|
||||
bestExchange = exchange;
|
||||
|
@ -497,14 +494,21 @@ module.exports = function(logger){
|
|||
});
|
||||
});
|
||||
logger.debug(logSystem, 'RESULT', 'Best coin for ' + algo + ' is ' + bestCoin + ' on ' + bestExchange + ' with ' + bestBtcPerMhPerHour.toFixed(8) + ' BTC/day per Mh/s');
|
||||
if (portalConfig.coinSwitchListener.enabled){
|
||||
var client = net.connect(portalConfig.coinSwitchListener.port, portalConfig.coinSwitchListener.host, function () {
|
||||
client.write(JSON.stringify({
|
||||
password: portalConfig.coinSwitchListener.password,
|
||||
coin: bestCoin
|
||||
}) + '\n');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var client = net.connect(portalConfig.cliPort, function () {
|
||||
client.write(JSON.stringify({
|
||||
command: 'coinswitch',
|
||||
params: [bestCoin],
|
||||
options: {algorithm: algo}
|
||||
}) + '\n');
|
||||
}).on('error', function(error){
|
||||
if (error.code === 'ECONNREFUSED')
|
||||
logger.error(logSystem, 'CLI', 'Could not connect to NOMP instance on port ' + portalConfig.cliPort);
|
||||
else
|
||||
logger.error(logSystem, 'CLI', 'Socket error ' + JSON.stringify(error));
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -148,7 +148,11 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
|||
symbol: poolConfigs[coinName].coin.symbol.toUpperCase(),
|
||||
algorithm: poolConfigs[coinName].coin.algorithm,
|
||||
hashrates: replies[i + 1],
|
||||
poolStats: replies[i + 2] != null ? replies[i + 2] : { validShares: 0, validBlocks: 0, invalidShares: 0 },
|
||||
poolStats: {
|
||||
validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0,
|
||||
validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0,
|
||||
invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0
|
||||
},
|
||||
blocks: {
|
||||
pending: replies[i + 3],
|
||||
confirmed: replies[i + 4],
|
||||
|
@ -191,9 +195,10 @@ module.exports = function(logger, portalConfig, poolConfigs){
|
|||
else
|
||||
coinStats.workers[worker] = workerShares;
|
||||
});
|
||||
var shareMultiplier = algos[coinStats.algorithm].multiplier || 0;
|
||||
var hashratePre = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;
|
||||
coinStats.hashrate = hashratePre | 0;
|
||||
|
||||
var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier;
|
||||
coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow;
|
||||
|
||||
coinStats.workerCount = Object.keys(coinStats.workers).length;
|
||||
portalStats.global.workers += coinStats.workerCount;
|
||||
|
||||
|
|
102
libs/website.js
102
libs/website.js
|
@ -3,18 +3,24 @@ var fs = require('fs');
|
|||
var path = require('path');
|
||||
|
||||
var async = require('async');
|
||||
var watch = require('node-watch');
|
||||
var redis = require('redis');
|
||||
|
||||
var dot = require('dot');
|
||||
var express = require('express');
|
||||
var bodyParser = require('body-parser');
|
||||
var compress = require('compression');
|
||||
|
||||
var watch = require('node-watch');
|
||||
var Stratum = require('stratum-pool');
|
||||
var util = require('stratum-pool/lib/util.js');
|
||||
|
||||
var api = require('./api.js');
|
||||
|
||||
|
||||
module.exports = function(logger){
|
||||
|
||||
dot.templateSettings.strip = false;
|
||||
|
||||
var portalConfig = JSON.parse(process.env.portalConfig);
|
||||
var poolConfigs = JSON.parse(process.env.pools);
|
||||
|
||||
|
@ -31,8 +37,10 @@ module.exports = function(logger){
|
|||
'home.html': '',
|
||||
'getting_started.html': 'getting_started',
|
||||
'stats.html': 'stats',
|
||||
'tbs.html': 'tbs',
|
||||
'api.html': 'api',
|
||||
'admin.html': 'admin'
|
||||
'admin.html': 'admin',
|
||||
'mining_key.html': 'mining_key'
|
||||
};
|
||||
|
||||
var pageTemplates = {};
|
||||
|
@ -40,6 +48,9 @@ module.exports = function(logger){
|
|||
var pageProcessed = {};
|
||||
var indexesProcessed = {};
|
||||
|
||||
var keyScriptTemplate = '';
|
||||
var keyScriptProcessed = '';
|
||||
|
||||
|
||||
var processTemplates = function(){
|
||||
|
||||
|
@ -112,6 +123,87 @@ module.exports = function(logger){
|
|||
setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000);
|
||||
|
||||
|
||||
var buildKeyScriptPage = function(){
|
||||
async.waterfall([
|
||||
function(callback){
|
||||
var client = redis.createClient(portalConfig.redis.port, portalConfig.redis.host);
|
||||
client.hgetall('coinVersionBytes', function(err, coinBytes){
|
||||
if (err){
|
||||
client.quit();
|
||||
return callback('Failed grabbing coin version bytes from redis ' + JSON.stringify(err));
|
||||
}
|
||||
callback(null, client, coinBytes || {});
|
||||
});
|
||||
},
|
||||
function (client, coinBytes, callback){
|
||||
var enabledCoins = Object.keys(poolConfigs).map(function(c){return c.toLowerCase()});
|
||||
var missingCoins = [];
|
||||
enabledCoins.forEach(function(c){
|
||||
if (!(c in coinBytes))
|
||||
missingCoins.push(c);
|
||||
});
|
||||
callback(null, client, coinBytes, missingCoins);
|
||||
},
|
||||
function(client, coinBytes, missingCoins, callback){
|
||||
var coinsForRedis = {};
|
||||
async.each(missingCoins, function(c, cback){
|
||||
var coinInfo = (function(){
|
||||
for (var pName in poolConfigs){
|
||||
if (pName.toLowerCase() === c)
|
||||
return {
|
||||
daemon: poolConfigs[pName].shareProcessing.internal.daemon,
|
||||
address: poolConfigs[pName].address
|
||||
}
|
||||
}
|
||||
})();
|
||||
var daemon = new Stratum.daemon.interface([coinInfo.daemon]);
|
||||
daemon.cmd('dumpprivkey', [coinInfo.address], function(result){
|
||||
if (result[0].error){
|
||||
logger.error(logSystem, 'daemon', 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error));
|
||||
cback();
|
||||
return;
|
||||
}
|
||||
|
||||
var vBytePub = util.getVersionByte(coinInfo.address)[0];
|
||||
var vBytePriv = util.getVersionByte(result[0].response)[0];
|
||||
|
||||
coinBytes[c] = vBytePub.toString() + ',' + vBytePriv.toString();
|
||||
coinsForRedis[c] = coinBytes[c];
|
||||
cback();
|
||||
});
|
||||
}, function(err){
|
||||
callback(null, client, coinBytes, coinsForRedis);
|
||||
});
|
||||
},
|
||||
function(client, coinBytes, coinsForRedis, callback){
|
||||
if (Object.keys(coinsForRedis).length > 0){
|
||||
client.hmset('coinVersionBytes', coinsForRedis, function(err){
|
||||
if (err)
|
||||
logger.error(logSystem, 'Init', 'Failed inserting coin byte version into redis ' + JSON.stringify(err));
|
||||
client.quit();
|
||||
});
|
||||
}
|
||||
else{
|
||||
client.quit();
|
||||
}
|
||||
callback(null, coinBytes);
|
||||
}
|
||||
], function(err, coinBytes){
|
||||
if (err){
|
||||
logger.error(logSystem, 'Init', err);
|
||||
return;
|
||||
}
|
||||
try{
|
||||
keyScriptTemplate = dot.template(fs.readFileSync('website/key.html', {encoding: 'utf8'}));
|
||||
keyScriptProcessed = keyScriptTemplate({coins: coinBytes});
|
||||
}
|
||||
catch(e){
|
||||
logger.error(logSystem, 'Init', 'Failed to read key.html file');
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
buildKeyScriptPage();
|
||||
|
||||
var getPage = function(pageId){
|
||||
if (pageId in pageProcessed){
|
||||
|
@ -146,6 +238,10 @@ module.exports = function(logger){
|
|||
next();
|
||||
});
|
||||
|
||||
app.get('/key.html', function(reg, res, next){
|
||||
res.end(keyScriptProcessed);
|
||||
});
|
||||
|
||||
app.get('/:page', route);
|
||||
app.get('/', route);
|
||||
|
||||
|
@ -181,4 +277,4 @@ module.exports = function(logger){
|
|||
});
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "node-open-mining-portal",
|
||||
"version": "0.0.3",
|
||||
"version": "0.0.4",
|
||||
"description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool",
|
||||
"keywords": [
|
||||
"stratum",
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -16,76 +16,69 @@ Simple lightweight & fast - a more efficient block notify script in pure C.
|
|||
|
||||
(may also work as coin switch)
|
||||
|
||||
Platforms : Linux,BSD,Solaris (mostly OS independent)
|
||||
Platforms : Linux, BSD, Solaris (mostly OS independent)
|
||||
|
||||
Build with:
|
||||
gcc blocknotify.c -o blocknotify
|
||||
|
||||
|
||||
Usage in daemon coin.conf
|
||||
blocknotify="/bin/blocknotify 127.0.0.1:8117 mySuperSecurePassword dogecoin %s"
|
||||
Example usage in daemon coin.conf using default NOMP CLI port of 17117
|
||||
blocknotify="/bin/blocknotify 127.0.0.1:17117 dogecoin %s"
|
||||
|
||||
*NOTE* If you use "localhost" as hostname you may get a "13" error (socket / connect / send may consider "localhost" as a broadcast address)
|
||||
|
||||
// {"password":"notepas","coin":"Xcoin","hash":"d2191a8b644c9cd903439edf1d89ee060e196b3e116e0d48a3f11e5e3987a03b"}
|
||||
// simplest connect + send json string to server
|
||||
|
||||
# $Id: blocknotify.c,v 0.1 2014/04/07 22:38:09 sysman Exp $
|
||||
*/
|
||||
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int sockfd,n;
|
||||
struct sockaddr_in servaddr,cliaddr;
|
||||
char sendline[1000];
|
||||
char recvline[1000];
|
||||
char host[200];
|
||||
char *p,*arg,*errptr;
|
||||
int port;
|
||||
int sockfd,n;
|
||||
struct sockaddr_in servaddr, cliaddr;
|
||||
char sendline[1000];
|
||||
char recvline[1000];
|
||||
char host[200];
|
||||
char *p, *arg, *errptr;
|
||||
int port;
|
||||
|
||||
if (argc < 4)
|
||||
{
|
||||
// print help
|
||||
printf("NOMP pool block notify\n usage: <host:port> <password> <coin> <block>\n");
|
||||
exit(1);
|
||||
}
|
||||
if (argc < 3)
|
||||
{
|
||||
// print help
|
||||
printf("NOMP pool block notify\n usage: <host:port> <coin> <block>\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
strncpy(host,argv[1],(sizeof(host)-1));
|
||||
p=host;
|
||||
strncpy(host, argv[1], (sizeof(host)-1));
|
||||
p = host;
|
||||
|
||||
if ( (arg=strchr(p,':')) )
|
||||
{
|
||||
*arg='\0';
|
||||
if ( (arg = strchr(p,':')) )
|
||||
{
|
||||
*arg = '\0';
|
||||
|
||||
errno=0; // reset errno
|
||||
port=strtol(++arg,&errptr,10);
|
||||
errno = 0; // reset errno
|
||||
port = strtol(++arg, &errptr, 10);
|
||||
|
||||
if ( (errno != 0) || (errptr == arg) ) { fprintf(stderr, "port number fail [%s]\n",errptr); }
|
||||
// if(strlen(arg) > (errptr-arg) ) also fail, but we ignore it for now
|
||||
// printf("host %s:%d\n",host,port);
|
||||
}
|
||||
if ( (errno != 0) || (errptr == arg) )
|
||||
{
|
||||
fprintf(stderr, "port number fail [%s]\n", errptr);
|
||||
}
|
||||
|
||||
// printf("pass: %s coin: %s block:[%s]\n",argv[2],argv[3],argv[4]);
|
||||
snprintf(sendline,sizeof(sendline)-1,
|
||||
"{\"password\":\"%s\",\"coin\":\"%s\",\"hash\":\"%s\"}\n",
|
||||
argv[2], argv[3], argv[4]);
|
||||
}
|
||||
|
||||
// printf("sendline:[%s]",sendline);
|
||||
snprintf(sendline, sizeof(sendline) - 1, "{\"command\":\"blocknotify\",\"params\":[\"%s\",\"%s\"]}\n", argv[2], argv[3]);
|
||||
|
||||
sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
|
||||
bzero(&servaddr,sizeof(servaddr));
|
||||
servaddr.sin_family = AF_INET;
|
||||
servaddr.sin_addr.s_addr=inet_addr(host);
|
||||
servaddr.sin_port=htons(port);
|
||||
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
|
||||
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
bzero(&servaddr, sizeof(servaddr));
|
||||
servaddr.sin_family = AF_INET;
|
||||
servaddr.sin_addr.s_addr = inet_addr(host);
|
||||
servaddr.sin_port = htons(port);
|
||||
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
|
||||
|
||||
int result = send(sockfd,sendline,strlen(sendline),0);
|
||||
close(sockfd);
|
||||
int result = send(sockfd, sendline, strlen(sendline), 0);
|
||||
close(sockfd);
|
||||
|
||||
if(result == -1) {
|
||||
printf("Error sending: %i\n",errno);
|
||||
if(result == -1) {
|
||||
printf("Error sending: %i\n", errno);
|
||||
exit(-1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
|
|
@ -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 () {
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -42,7 +42,13 @@
|
|||
<li class="{{? it.selected === 'stats' }}pure-menu-selected{{?}}">
|
||||
<a class="hot-swapper" href="/stats">
|
||||
<i class="fa fa-bar-chart-o"></i>
|
||||
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>
|
||||
Tab Stats
|
||||
</a>
|
||||
</li>
|
||||
<li class="{{? it.selected === 'api' }}pure-menu-selected{{?}}">
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +1,10 @@
|
|||
<div>
|
||||
API Docs here
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>/stats - raw json statistic</li>
|
||||
<li>/pool_stats - historical time per pool json </li>
|
||||
<li>/live_stats - live stats </li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue