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