├── .gitignore ├── AEON-Notes.md ├── LICENSE ├── README.md ├── SQL_MIGRATIONS.md ├── coinConfig.json ├── config_example.json ├── debug_scripts ├── block_add.js ├── block_locker.js ├── block_migrator_from_old_sql.js ├── block_share_fix.js ├── block_unlocker.js └── socket_io.html ├── deployment ├── aeon.service ├── base.sql ├── caddyfile ├── deploy.bash ├── deploy_aeon.bash ├── install_lmdb_tools.sh ├── leaf.bash ├── monero.service ├── monero_daemon.patch └── upgrade_monero.bash ├── init.js ├── lib ├── api.js ├── blockManager.js ├── coins │ ├── aeon-rebase.js │ ├── aeon.js │ ├── krb.js │ └── xmr.js ├── data.proto ├── local_comms.js ├── longRunner.js ├── payment_systems │ ├── aeon-rebase.js │ ├── aeon.js │ ├── krb.js │ └── xmr.js ├── payments.js ├── pool.js ├── remoteShare.js ├── remote_comms.js ├── support.js └── worker.js ├── package.json ├── sample_config.sql ├── sql_sync ├── config_entries.json └── sql_sync.js └── tools └── blocks.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 3 | 4 | # User-specific stuff: 5 | .idea 6 | 7 | ## File-based project format: 8 | *.iws 9 | 10 | ## Plugin-specific files: 11 | 12 | # IntelliJ 13 | /out/ 14 | 15 | # mpeltonen/sbt-idea plugin 16 | .idea_modules/ 17 | 18 | # JIRA plugin 19 | atlassian-ide-plugin.xml 20 | 21 | # Crashlytics plugin (for Android Studio and IntelliJ) 22 | com_crashlytics_export_strings.xml 23 | crashlytics.properties 24 | crashlytics-build.properties 25 | fabric.properties 26 | 27 | #NodeJs Ignores 28 | 29 | # Logs 30 | logs 31 | *.log 32 | npm-debug.log* 33 | 34 | # Runtime data 35 | pids 36 | *.pid 37 | *.seed 38 | *.pid.lock 39 | 40 | # Directory for instrumented libs generated by jscoverage/JSCover 41 | lib-cov 42 | 43 | # Coverage directory used by tools like istanbul 44 | coverage 45 | 46 | # nyc test coverage 47 | .nyc_output 48 | 49 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 50 | .grunt 51 | 52 | # node-waf configuration 53 | .lock-wscript 54 | 55 | # Compiled binary addons (http://nodejs.org/api/addons.html) 56 | build/Release 57 | 58 | # Dependency directories 59 | node_modules 60 | jspm_packages 61 | 62 | # Optional npm cache directory 63 | .npm 64 | 65 | # Optional eslint cache 66 | .eslintcache 67 | 68 | # Optional REPL history 69 | .node_repl_history 70 | 71 | # Output of 'npm pack' 72 | *.tgz 73 | 74 | # Yarn Integrity file 75 | .yarn-integrity 76 | 77 | config.json 78 | -------------------------------------------------------------------------------- /AEON-Notes.md: -------------------------------------------------------------------------------- 1 | # Notes about AEON coin 2 | 3 | #### Special considerations for AEON 4 | 5 | NodeJS-Pool and PoolUI were built with monero in mind. While a recent update has made the pool compatible with Cryptonight-lite, the UI itself, by another developer, was hardcoded for Monero. Due to this there's a few things that need to be changed for the pool to successfully work. 6 | 7 | - aeond does not have a blockchain import function. So this makes downloading a blockchain.bin and importing it a bit difficult to do. I have included in my deploy.bash script a url i'm providing to keep the blockchain up to date within atleast a week delay, but it's unusable at the moment 8 | - Until the PoolUI can be updated to allow for coins to be changed via a config, we're going to need to use a separate fork that I'll provide. I'll be working to try to include what I can to make the fork work for Monero AND aeon, but for the purpose here, my main goal will be for Aeon's compatibility. 9 | - NodeJS-Pool is a bit more complex than CryptoNote Uni Pool, but it appears to be working just fine for our purpsoes. I highly recommend testing it for 1-2blocks if possible. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Snipa22 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | This work is a derivitive work from Zone117x's node-cryptonote-pool, permission 24 | has been granted from Zone117x via IRC conversation to re-license this code as 25 | MIT for all portions of the work derived from his original source as of 2/11/2017 26 | Original work: https://github.com/zone117x/node-cryptonote-pool 27 | 28 | Log snippet: 29 | Feb 11 15:59:02 Hallo! Wanted to reach out as we finally released the pool software w/ the FFS funded at this point. https://github.com/Snipa22/nodejs-pool . The following code: https://pb.junaos.xyz/view/cd3d7e27 is what was snagged, and mostly rebuilt along the way for good measure. 30 | Feb 11 15:59:02 I'm not sure how deeply the GPL requires me to go to claim it as "new code" given that the logic behind it all is about the same. 31 | 32 | Feb 11 16:09:39 .fr is now 1/5th of the overall mining power, DP is another 1/5th. 33 | Feb 11 16:09:48 We're getting scary-close to the 50% attacks. 34 | Feb 11 16:10:08 You have my permission to MIT 35 | Feb 11 16:10:31 Thank you. I'll get our licenses updated and such today. 36 | Feb 11 16:12:08 What payment systems did you get implemented? 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pool Design/Theory 2 | ================== 3 | The nodejs-pool is built around a small series of core daemons that share access to a single LMDB table for tracking of shares, with MySQL being used to centralize configurations and ensure simple access from local/remote nodes. The core daemons follow: 4 | ```text 5 | api - Main API for the frontend to use and pull data from. Expects to be hosted at / 6 | remoteShare - Main API for consuming shares from remote/local pools. Expects to be hosted at /leafApi 7 | pool - Where the miners connect to. 8 | longRunner - Database share cleanup. 9 | payments - Handles all payments to workers. 10 | blockManager - Unlocks blocks and distributes payments into MySQL 11 | worker - Does regular processing of statistics and sends status e-mails for non-active miners. 12 | ``` 13 | API listens on port 8001, remoteShare listens on 8000 14 | 15 | Xmrpool.net (The reference implementation) uses the following setup: 16 | * https://xmrpool.net is hosted on its own server, as the main website is a static frontend 17 | * https://api.xmrpool.net hosts api, remoteShare, longRunner, payments, blockManager, worker, as these must all be hosted with access to the same LMDB database. 18 | 19 | Sample Caddyfile for API: 20 | ```text 21 | https://api.xmrpool.net { 22 | proxy /leafApi 127.0.0.1:8000 23 | proxy / 127.0.0.1:8001 24 | cors 25 | gzip 26 | } 27 | ``` 28 | 29 | It is critically important that your webserver does not truncate the `/leafApi` portion of the URL for the remoteShare daemon, or it will not function! Local pool servers DO use the remoteShare daemon, as this provides a buffer in case of an error with LMDB or another bug within the system, allowing shares and blocks to queue for submission as soon as the leafApi/remoteShare daemons are back up and responding with 200's. 30 | 31 | Setup Instructions 32 | ================== 33 | 34 | Server Requirements 35 | ------------------- 36 | * 4 Gb Ram 37 | * 2 CPU Cores (with AES_NI) 38 | * 60 Gb SSD-Backed Storage - If you're doing a multi-server install, the leaf nodes do not need this much storage. They just need enough storage to hold the blockchain for your node. The pool comes configured to use up to 24Gb of storage for LMDB. Assuming you have the longRunner worker running, it should never get near this size, but be aware that it /can/ bloat readily if things error, so be ready for this! 39 | * Notably, this happens to be approximately the size of a 4Gb linode instance, which is where the majority of automated deployment testing happened! 40 | 41 | Pre-Deploy 42 | ---------- 43 | * If you're planning on using e-mail, you'll want to setup an account at https://mailgun.com (It's free for 10k e-mails/month!), so you can notify miners. This also serves as the backend for password reset emails, along with other sorts of e-mails from the pool, including pool startup, pool Monerod daemon lags, etc so it's highly suggested! 44 | * Pre-Generate the wallets, or don't, it's up to you! You'll need the addresses after the install is complete, so I'd suggest making sure you have them available. Information on suggested setups are found below. 45 | * If you're going to be offering PPS, PLEASE make sure you load the pool wallet with XMR before you get too far along. Your pool will trigger PPS payments on its own, and fairly readily, so you need some float in there! 46 | * Make a non-root user, and run the installer from there! 47 | 48 | Deployment via Installer 49 | ------------------------ 50 | 51 | 1. Add your user to `/etc/sudoers`, this must be done so the script can sudo up and do it's job. We suggest passwordless sudo. Suggested line: ` ALL=(ALL) NOPASSWD:ALL`. Our sample builds use: `pooldaemon ALL=(ALL) NOPASSWD:ALL` 52 | 2. Run the [deploy script](https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/deploy.bash) as a **NON-ROOT USER**. This is very important! This script will install the pool to whatever user it's running under! Also. Go get a coffee, this sucker bootstraps the monero installation. 53 | 3. Once it's complete, change as `config.json` appropriate. It is pre-loaded for a local install of everything, running on 127.0.0.1. This will work perfectly fine if you're using a single node setup. You'll also want to set `bind_ip` to the external IP of the pool server, and `hostname` to the resolvable hostname for the pool server. `pool_id` is mostly used for multi-server installations to provide unique identifiers in the backend. You will also want to run: source ~/.bashrc This will activate NVM and get things working for the following pm2 steps. 54 | 4. You'll need to change the API endpoint for the frontend code in the `poolui/build/globals.js` and `poolui/build/globals.default.js` -- This will usually be `http(s):///api` unless you tweak caddy! 55 | 5. The default database directory `/home//pool_db/` is already been created during startup. If you change the `db_storage_path` just make sure your user has write permissions for new path. Run: `pm2 restart api` to reload the API for usage. 56 | 6. Hop into the web interface (Should be at `http:///admin.html`), then login with `Administrator/Password123`, **MAKE SURE TO CHANGE THIS PASSWORD ONCE YOU LOGIN**. *<- This step is currently not active, we're waiting for the frontend to catch up! Head down to the Manual SQL Configuration to take a look at what needs to be done by hand for now*. 57 | 7. From the admin panel, you can configure all of your pool's settings for addresses, payment thresholds, etc. 58 | 8. Once you're happy with the settings, go ahead and start all the pool daemons, commands follow. 59 | 60 | ```shell 61 | cd ~/nodejs-pool/ 62 | pm2 start init.js --name=blockManager --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=blockManager 63 | pm2 start init.js --name=worker --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=worker 64 | pm2 start init.js --name=payments --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=payments 65 | pm2 start init.js --name=remoteShare --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=remoteShare 66 | pm2 start init.js --name=longRunner --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=longRunner 67 | pm2 start init.js --name=pool --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=pool 68 | pm2 restart api 69 | ``` 70 | 71 | Install Script: 72 | ```bash 73 | curl -L https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/deploy.bash | bash 74 | ``` 75 | 76 | Assumptions for the installer 77 | ----------------------------- 78 | The installer assumes that you will be running a single-node instance and using a clean Ubuntu 16.04 server install. The following system defaults are set: 79 | * MySQL Username: pool 80 | * MySQL Password: 98erhfiuehw987fh23d 81 | * MySQL Host: 127.0.0.1 82 | * MySQL root access is only permitted as the root user, the password is in `/root/.my.cnf` 83 | * SSL Certificate is generated, self-signed, but is valid for Claymore Miners. 84 | * The server installs and deploys Caddy as it's choice of web server! 85 | 86 | The following raw binaries **MUST BE AVAILABLE FOR IT TO BOOTSTRAP**: 87 | * sudo 88 | 89 | I've confirmed that the default server 16.04 installation has these requirements. 90 | 91 | The pool comes pre-configured with values for Monero (XMR), these may need to be changed depending on the exact requirements of your coin. Other coins will likely be added down the road, and most likely will have configuration.sqls provided to overwrite the base configurations for their needs, but can be configured within the frontend as well. 92 | 93 | The pool ALSO applies a series of patches: Fluffy Blocks, Additional Open P2P Connections, 128 Txn Bug Fix. If you don't like these, replace the auto-installed monerod fixes! 94 | 95 | Wallet Setup 96 | ------------ 97 | The pool is designed to have a dual-wallet design, one which is a fee wallet, one which is the live pool wallet. The fee wallet is the default target for all fees owed to the pool owner. PM2 can also manage your wallet daemon, and that is the suggested run state. 98 | 99 | 1. Generate your wallets using `/usr/local/src/monero/build/release/bin/monero-wallet-cli` 100 | 2. Make sure to save your regeneration stuff! 101 | 3. For the pool wallet, store the password in a file, the suggestion is `~/wallet_pass` 102 | 4. Change the mode of the file with chmod to 0400: `chmod 0400 ~/wallet_pass` 103 | 5. Start the wallet using PM2: `pm2 start /usr/local/src/monero/build/release/bin/monero-wallet-rpc -- --rpc-bind-port 18082 --password-file ~/wallet_pass --wallet-file --disable-rpc-login --trusted-daemon` 104 | 6. If you don't use PM2, then throw the wallet into a screen and have fun. 105 | 106 | Manual Setup 107 | ------------ 108 | Pretty similar to the above, you may wish to dig through a few other things for sanity sake, but the installer scripts should give you a good idea of what to expect from the ground up. 109 | 110 | Manual SQL Configuration 111 | ------------------------ 112 | Until the full frontend is released, the following SQL information needs to be updated by hand in order to bring your pool online, in module/item format. You can also edit the values in sample_config.sql, then import them into SQL directly via an update. 113 | ``` 114 | Critical/Must be done: 115 | pool/address 116 | pool/feeAddress 117 | general/shareHost 118 | 119 | Nice to have: 120 | general/mailgunKey 121 | general/mailgunURL 122 | general/emailFrom 123 | 124 | SQL import command: sudo mysql pool < ~/nodejs-pool/sample_config.sql (Adjust name/path as needed!) 125 | ``` 126 | 127 | The shareHost configuration is designed to be pointed at wherever the leafApi endpoint exists. For xmrpool.net, we use https://api.xmrpool.net/leafApi. If you're using the automated setup script, you can use: `http:///leafApi`, as Caddy will proxy it. If you're just using localhost and a local pool serv, http://127.0.0.1:8000/leafApi will do you quite nicely 128 | 129 | Additional ports can be added as desired, samples can be found at the end of base.sql. If you're not comfortable with the MySQL command line, I highly suggest MySQL Workbench or a similar piece of software (I use datagrip!). Your root MySQL password can be found in `/root/.my.cnf` 130 | 131 | Final Manual Steps 132 | ------------------ 133 | Until the main frontend is done, we suggest running the following SQL line: 134 | ``` 135 | DELETE FROM pool.users WHERE username = 'Administrator'; 136 | ``` 137 | This will remove the administrator user until there's an easier way to change the password. Alternatively, you can change the password to something not known by the public: 138 | ``` 139 | UPDATE pool.users SET email='your new password here' WHERE username='Administrator'; 140 | ``` 141 | The email field is used as the default password field until the password is changed, at which point, it's hashed and dumped into the password field instead, and using the email field as a password is disabled. 142 | 143 | You should take a look at the [wiki](https://github.com/Snipa22/nodejs-pool/wiki/Configuration-Details) for specific configuration settings in the system. 144 | 145 | Pool Update Procedures 146 | ====================== 147 | If upgrading the pool, please do a git pull to get the latest code within the pool's directory. 148 | 149 | Once complete, please `cd` into `sql_sync`, then run `node sql_sync.js` 150 | 151 | This will update your pool with the latest config options with any defaults that the pools may set. 152 | 153 | Pool Troubleshooting 154 | ==================== 155 | 156 | API stopped updating! 157 | --------------------- 158 | This is likely due to LMDB's MDB_SIZE being hit, or due to LMDB locking up due to a reader staying open too long, possibly due to a software crash. 159 | The first step is to run: 160 | ``` 161 | mdb_stat -fear ~/pool_db/ 162 | ``` 163 | This should give you output like: 164 | ``` 165 | Environment Info 166 | Map address: (nil) 167 | Map size: 51539607552 168 | Page size: 4096 169 | Max pages: 12582912 170 | Number of pages used: 12582904 171 | Last transaction ID: 74988258 172 | Max readers: 512 173 | Number of readers used: 24 174 | Reader Table Status 175 | pid thread txnid 176 | 25763 7f4f0937b740 74988258 177 | Freelist Status 178 | Tree depth: 3 179 | Branch pages: 135 180 | Leaf pages: 29917 181 | Overflow pages: 35 182 | Entries: 591284 183 | Free pages: 12234698 184 | Status of Main DB 185 | Tree depth: 1 186 | Branch pages: 0 187 | Leaf pages: 1 188 | Overflow pages: 0 189 | Entries: 3 190 | Status of blocks 191 | Tree depth: 1 192 | Branch pages: 0 193 | Leaf pages: 1 194 | Overflow pages: 0 195 | Entries: 23 196 | Status of cache 197 | Tree depth: 3 198 | Branch pages: 16 199 | Leaf pages: 178 200 | Overflow pages: 2013 201 | Entries: 556 202 | Status of shares 203 | Tree depth: 2 204 | Branch pages: 1 205 | Leaf pages: 31 206 | Overflow pages: 0 207 | Entries: 4379344 208 | ``` 209 | The important thing to verify here is that the "Number of pages used" value is less than the "Max Pages" value, and that there are "Free pages" under "Freelist Status". If this is the case, them look at the "Reader Table Status" and look for the PID listed. Run: 210 | ```shell 211 | ps fuax | grep 212 | 213 | ex: 214 | ps fuax | grep 25763 215 | ``` 216 | If the output is not blank, then one of your node processes is reading, this is fine. If there is no output given on one of them, then proceed forwards. 217 | 218 | The second step is to run: 219 | ```shell 220 | pm2 stop blockManager worker payments remoteShare longRunner api 221 | pm2 start blockManager worker payments remoteShare longRunner api 222 | ``` 223 | This will restart all of your related daemons, and will clear any open reader connections, allowing LMDB to get back to a normal state. 224 | 225 | If on the other hand, you have no "Free pages" and your Pages used is equal to the Max Pages, then you've run out of disk space for LMDB. You need to verify the cleaner is working. For reference, 4.3 million shares are stored within approximately 2-3 Gb of space, so if you're vastly exceeding this, then your cleaner (longRunner) is likely broken. 226 | 227 | 228 | PPS Fee Thoughts 229 | ================ 230 | If you're considering PPS, I've spoken with [Fireice_UK](https://github.com/fireice-uk/) whom kindly did some math about what you're looking at in terms of requirements to run a PPS pool without it self-imploding under particular risk factors, based on the work found [here](https://arxiv.org/pdf/1112.4980.pdf) 231 | 232 | ```text 233 | Also I calculated the amount of XMR needed to for a PPS pool to stay afloat. Perhaps you should put them up in the README to stop some spectacular clusterfucks :D: 234 | For 1 in 1000000 chance that the pool will go bankrupt: 5% fee -> 1200 2% fee -> 3000 235 | For 1 in 1000000000 chance: 5% fee -> 1800 2% fee -> 4500 236 | ``` 237 | 238 | The developers of the pool have not verified this, but based on our own usage on https://xmrpool.net/ this seems rather reasonable. You should be wary if you're considering PPS and take you fees into account appropriately! 239 | 240 | Installation/Configuration Assistance 241 | ===================================== 242 | If you need help installing the pool from scratch, please have your servers ready, which would be Ubuntu 16.04 servers, blank and clean, DNS records pointed. These need to be x86_64 boxes with AES-NI Available. 243 | 244 | Installation assistance is 7 XMR, with a 3 XMR deposit, with remainder to be paid on completion. 245 | Configuration assistance is 4 XMR with a 2 XMR deposit, and includes debugging your pool configurations, ensuring that everything is running, and tuning for your uses/needs. 246 | 247 | SSH access with a sudo-enabled user will be needed, preferably the user that is slated to run the pool. 248 | 249 | If you'd like assistance with setting up node-cryptonote-pool, please provide what branch/repo you'd like to work from, as there's a variety of these. 250 | 251 | Assistance is not available for frontend customization at this time. 252 | 253 | For assistance, please contact Snipa at pool_install@snipanet.com or via IRC at irc.freenode.net in the #monero-pools channel. 254 | 255 | Developer Donations 256 | =================== 257 | If you'd like to make a one time donation, the addresses are as follows: 258 | * XMR - 44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr 259 | * BTC - 114DGE2jmPb5CP2RGKZn6u6xtccHhZGFmM 260 | * AEON - WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu 261 | 262 | Credits 263 | ======= 264 | 265 | [Zone117x](https://github.com/zone117x) - Original [node-cryptonote-pool](https://github.com/zone117x/node-cryptonote-pool) from which, the stratum implementation has been borrowed. 266 | 267 | [Mesh00](https://github.com/mesh0000) - Frontend build in Angular JS [XMRPoolUI](https://github.com/mesh0000/poolui) 268 | 269 | [Wolf0](https://github.com/wolf9466/)/[OhGodAGirl](https://github.com/ohgodagirl) - Rebuild of node-multi-hashing with AES-NI [node-multi-hashing](https://github.com/Snipa22/node-multi-hashing-aesni) 270 | -------------------------------------------------------------------------------- /SQL_MIGRATIONS.md: -------------------------------------------------------------------------------- 1 | 2/12/2017 2 | --------- 3 | ```sql 4 | ALTER TABLE pool.config MODIFY item_value TEXT; 5 | ``` 6 | 7 | 2/13/2017 8 | --------- 9 | ```sql 10 | ALTER TABLE pool.payments ADD transfer_fee BIGINT(20) DEFAULT 0 NULL; 11 | ``` 12 | 13 | 2/16/2017 14 | --------- 15 | ```sql 16 | ALTER DATABASE pool DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 17 | ALTER TABLE pool.balance CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 18 | ALTER TABLE pool.bans CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 19 | ALTER TABLE pool.block_log CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 20 | ALTER TABLE pool.config CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 21 | ALTER TABLE pool.payments CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 22 | ALTER TABLE pool.pools CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 23 | ALTER TABLE pool.port_config CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 24 | ALTER TABLE pool.ports CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 25 | ALTER TABLE pool.shapeshiftTxn CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 26 | ALTER TABLE pool.transactions CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 27 | ALTER TABLE pool.users CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 28 | ALTER TABLE pool.xmrtoTxn CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; 29 | ``` 30 | 31 | 2/25/2017 32 | --------- 33 | ```sql 34 | ALTER TABLE pool.users ADD enable_email BOOL DEFAULT true NULL; 35 | ``` -------------------------------------------------------------------------------- /coinConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "xmr": { 3 | "funcFile": "./lib/coins/xmr.js", 4 | "paymentFile": "./payment_systems/xmr.js", 5 | "sigDigits": 1000000000000, 6 | "shapeshift": "xmr_btc", 7 | "xmrTo": true, 8 | "name": "Monero", 9 | "mixIn": 4, 10 | "shortCode": "XMR" 11 | }, 12 | "krb": { 13 | "funcFile": "./lib/coins/krb.js", 14 | "paymentFile": "./payment_systems/krb.js", 15 | "sigDigits": 1000000000000, 16 | "name": "Karbowanec", 17 | "mixIn": 4, 18 | "shortCode": "KRB" 19 | }, 20 | "aeon": { 21 | "funcFile": "./lib/coins/aeon.js", 22 | "paymentFile": "./payment_systems/aeon.js", 23 | "sigDigits": 1000000000000, 24 | "name": "Aeon Coin", 25 | "mixIn": 4, 26 | "shortCode": "AEON" 27 | }, 28 | "aeon-rebase": { 29 | "funcFile": "./lib/coins/aeon-rebase.js", 30 | "paymentFile": "./payment_systems/aeon-rebase.js", 31 | "sigDigits": 1000000000000, 32 | "name": "Aeon Coin", 33 | "mixIn": 4, 34 | "shortCode": "AEON" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "pool_id": 0, 3 | "bind_ip": "127.0.0.1", 4 | "hostname": "testpool.com", 5 | "db_storage_path": "CHANGEME", 6 | "coin": "xmr", 7 | "mysql": { 8 | "connectionLimit": 20, 9 | "host": "127.0.0.1", 10 | "database": "pool", 11 | "user": "pool", 12 | "password": "98erhfiuehw987fh23d" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /debug_scripts/block_add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("../config.json"); 6 | let coinConfig = fs.readFileSync("../coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | const request = require('request'); 9 | 10 | global.support = require("../lib/support.js")(); 11 | global.config = JSON.parse(config); 12 | global.mysql = mysql.createPool(global.config.mysql); 13 | global.protos = protobuf(fs.readFileSync('../lib/data.proto')); 14 | let comms; 15 | let coinInc; 16 | 17 | 18 | // Config Table Layout 19 | // . 20 | 21 | global.mysql.query("SELECT * FROM config").then(function (rows) { 22 | rows.forEach(function (row){ 23 | if (!global.config.hasOwnProperty(row.module)){ 24 | global.config[row.module] = {}; 25 | } 26 | if (global.config[row.module].hasOwnProperty(row.item)){ 27 | return; 28 | } 29 | switch(row.item_type){ 30 | case 'int': 31 | global.config[row.module][row.item] = parseInt(row.item_value); 32 | break; 33 | case 'bool': 34 | global.config[row.module][row.item] = (row.item_value === "true"); 35 | break; 36 | case 'string': 37 | global.config[row.module][row.item] = row.item_value; 38 | break; 39 | case 'float': 40 | global.config[row.module][row.item] = parseFloat(row.item_value); 41 | break; 42 | } 43 | }); 44 | }).then(function(){ 45 | global.config['coin'] = JSON.parse(coinConfig)[global.config.coin]; 46 | coinInc = require("." + global.config.coin.funcFile); 47 | global.coinFuncs = new coinInc(); 48 | if (argv.module === 'pool'){ 49 | comms = require('../lib/remote_comms'); 50 | } else { 51 | comms = require('../lib/local_comms'); 52 | } 53 | global.database = new comms(); 54 | global.database.initEnv(); 55 | global.coinFuncs.blockedAddresses.push(global.config.pool.address); 56 | global.coinFuncs.blockedAddresses.push(global.config.payout.feeAddress); 57 | }).then(function(){ 58 | /* 59 | message Block { 60 | required string hash = 1; 61 | required int64 difficulty = 2; 62 | required int64 shares = 3; 63 | required int64 timestamp = 4; 64 | required POOLTYPE poolType = 5; 65 | required bool unlocked = 6; 66 | required bool valid = 7; 67 | optional int64 value = 8; 68 | } 69 | */ 70 | let invalidBlockProto = global.protos.Block.encode({ 71 | hash: "88cf2c37e1e4e8a273cbe3ec502b6975fd6c4ebe1e8889ad9d5e53a5e9cde007", 72 | difficulty: 1002932, 73 | shares: 0, 74 | timestamp: Date.now(), 75 | poolType: global.protos.POOLTYPE.PPS, 76 | unlocked: false, 77 | valid: true, 78 | value:0 79 | }); 80 | let wsData = global.protos.WSData.encode({ 81 | msgType: global.protos.MESSAGETYPE.BLOCK, 82 | key: global.config.api.authKey, 83 | msg: invalidBlockProto, 84 | exInt: 1 85 | }); 86 | request.post({url: global.config.general.shareHost, body: wsData}, function (error, response, body) { 87 | console.log(error); 88 | console.log(JSON.stringify(response)); 89 | console.log(JSON.stringify(body)); 90 | }); 91 | }); -------------------------------------------------------------------------------- /debug_scripts/block_locker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("../config.json"); 6 | let coinConfig = fs.readFileSync("../coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | 9 | global.support = require("../lib/support.js")(); 10 | global.config = JSON.parse(config); 11 | global.mysql = mysql.createPool(global.config.mysql); 12 | global.protos = protobuf(fs.readFileSync('../lib/data.proto')); 13 | let comms; 14 | comms = require('../lib/local_comms'); 15 | global.database = new comms(); 16 | global.database.initEnv(); 17 | global.database.lockBlock(argv.blockID); 18 | console.log("Block "+argv.blockID+" re-locked! Exiting!"); 19 | process.exit(); -------------------------------------------------------------------------------- /debug_scripts/block_migrator_from_old_sql.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("../config.json"); 6 | let coinConfig = fs.readFileSync("../coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | const request = require('request'); 9 | 10 | global.support = require("../lib/support.js")(); 11 | global.config = JSON.parse(config); 12 | global.mysql = mysql.createPool(global.config.mysql); 13 | global.protos = protobuf(fs.readFileSync('../lib/data.proto')); 14 | let comms; 15 | let coinInc; 16 | 17 | 18 | // Config Table Layout 19 | // . 20 | 21 | global.mysql.query("SELECT * FROM config").then(function (rows) { 22 | rows.forEach(function (row){ 23 | if (!global.config.hasOwnProperty(row.module)){ 24 | global.config[row.module] = {}; 25 | } 26 | if (global.config[row.module].hasOwnProperty(row.item)){ 27 | return; 28 | } 29 | switch(row.item_type){ 30 | case 'int': 31 | global.config[row.module][row.item] = parseInt(row.item_value); 32 | break; 33 | case 'bool': 34 | global.config[row.module][row.item] = (row.item_value === "true"); 35 | break; 36 | case 'string': 37 | global.config[row.module][row.item] = row.item_value; 38 | break; 39 | case 'float': 40 | global.config[row.module][row.item] = parseFloat(row.item_value); 41 | break; 42 | } 43 | }); 44 | }).then(function(){ 45 | global.config['coin'] = JSON.parse(coinConfig)[global.config.coin]; 46 | coinInc = require("." + global.config.coin.funcFile); 47 | global.coinFuncs = new coinInc(); 48 | if (argv.module === 'pool'){ 49 | comms = require('../lib/remote_comms'); 50 | } else { 51 | comms = require('../lib/local_comms'); 52 | } 53 | global.database = new comms(); 54 | global.database.initEnv(); 55 | global.coinFuncs.blockedAddresses.push(global.config.pool.address); 56 | global.coinFuncs.blockedAddresses.push(global.config.payout.feeAddress); 57 | }).then(function(){ 58 | /* 59 | message Block { 60 | required string hash = 1; 61 | required int64 difficulty = 2; 62 | required int64 shares = 3; 63 | required int64 timestamp = 4; 64 | required POOLTYPE poolType = 5; 65 | required bool unlocked = 6; 66 | required bool valid = 7; 67 | optional int64 value = 8; 68 | } 69 | */ 70 | global.mysql.query("SELECT * FROM blocks").then(function(rows){ 71 | rows.forEach(function(row){ 72 | let block = { 73 | hash: row.hex, 74 | difficulty: row.difficulty, 75 | shares: row.shares, 76 | timestamp: global.support.formatDateFromSQL(row.find_time)*1000, 77 | poolType: null, 78 | unlocked: row.unlocked === 1, 79 | valid: row.valid === 1 80 | }; 81 | switch(row.pool_type){ 82 | case 'pplns': 83 | block.poolType = global.protos.POOLTYPE.PPLNS; 84 | break; 85 | case 'solo': 86 | block.poolType = global.protos.POOLTYPE.SOLO; 87 | break; 88 | case 'prop': 89 | block.poolType = global.protos.POOLTYPE.PROP; 90 | break; 91 | case 'pps': 92 | block.poolType = global.protos.POOLTYPE.PPS; 93 | break; 94 | default: 95 | block.poolType = global.protos.POOLTYPE.PPLNS; 96 | } 97 | global.coinFuncs.getBlockHeaderByHash(block.hash, function(header){ 98 | block.value = header.reward; 99 | let txn = global.database.env.beginTxn(); 100 | txn.putBinary(global.database.blockDB, row.height, global.protos.Block.encode(block)); 101 | txn.commit(); 102 | }); 103 | }); 104 | }); 105 | }); -------------------------------------------------------------------------------- /debug_scripts/block_share_fix.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("../config.json"); 6 | let coinConfig = fs.readFileSync("../coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | const request = require('request'); 9 | 10 | global.support = require("../lib/support.js")(); 11 | global.config = JSON.parse(config); 12 | global.mysql = mysql.createPool(global.config.mysql); 13 | global.protos = protobuf(fs.readFileSync('../lib/data.proto')); 14 | let comms; 15 | let coinInc; 16 | 17 | 18 | // Config Table Layout 19 | // . 20 | 21 | global.mysql.query("SELECT * FROM config").then(function (rows) { 22 | rows.forEach(function (row){ 23 | if (!global.config.hasOwnProperty(row.module)){ 24 | global.config[row.module] = {}; 25 | } 26 | if (global.config[row.module].hasOwnProperty(row.item)){ 27 | return; 28 | } 29 | switch(row.item_type){ 30 | case 'int': 31 | global.config[row.module][row.item] = parseInt(row.item_value); 32 | break; 33 | case 'bool': 34 | global.config[row.module][row.item] = (row.item_value === "true"); 35 | break; 36 | case 'string': 37 | global.config[row.module][row.item] = row.item_value; 38 | break; 39 | case 'float': 40 | global.config[row.module][row.item] = parseFloat(row.item_value); 41 | break; 42 | } 43 | }); 44 | }).then(function(){ 45 | global.config['coin'] = JSON.parse(coinConfig)[global.config.coin]; 46 | coinInc = require("." + global.config.coin.funcFile); 47 | global.coinFuncs = new coinInc(); 48 | if (argv.module === 'pool'){ 49 | comms = require('../lib/remote_comms'); 50 | } else { 51 | comms = require('../lib/local_comms'); 52 | } 53 | global.database = new comms(); 54 | global.database.initEnv(); 55 | global.coinFuncs.blockedAddresses.push(global.config.pool.address); 56 | global.coinFuncs.blockedAddresses.push(global.config.payout.feeAddress); 57 | }).then(function(){ 58 | global.database.fixBlockShares(1241110); 59 | }); -------------------------------------------------------------------------------- /debug_scripts/block_unlocker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("../config.json"); 6 | let coinConfig = fs.readFileSync("../coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | 9 | global.support = require("../lib/support.js")(); 10 | global.config = JSON.parse(config); 11 | global.mysql = mysql.createPool(global.config.mysql); 12 | global.protos = protobuf(fs.readFileSync('../lib/data.proto')); 13 | let comms; 14 | comms = require('../lib/local_comms'); 15 | global.database = new comms(); 16 | global.database.initEnv(); 17 | global.database.unlockBlock(argv.blockHash); 18 | console.log("Block "+argv.blockHash+" un-locked! Exiting!"); 19 | process.exit(); 20 | -------------------------------------------------------------------------------- /debug_scripts/socket_io.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 22 | -------------------------------------------------------------------------------- /deployment/aeon.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Aeon Daemon 3 | After=network.target 4 | 5 | [Service] 6 | Type=forking 7 | GuessMainPID=no 8 | ExecStart=/usr/local/src/aeon/build/release/bin/aeon --rpc-bind-ip 127.0.0.1 --detach --restricted-rpc 9 | Restart=always 10 | User=aeondaemon 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /deployment/base.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE pool; 2 | GRANT ALL ON pool.* TO pool@`127.0.0.1` IDENTIFIED BY '98erhfiuehw987fh23d'; 3 | GRANT ALL ON pool.* TO pool@localhost IDENTIFIED BY '98erhfiuehw987fh23d'; 4 | FLUSH PRIVILEGES; 5 | USE pool; 6 | ALTER DATABASE pool DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; 7 | CREATE TABLE `balance` ( 8 | `id` int(11) NOT NULL AUTO_INCREMENT, 9 | `last_edited` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | `payment_address` varchar(128) DEFAULT NULL, 11 | `payment_id` varchar(128) DEFAULT NULL, 12 | `pool_type` varchar(64) DEFAULT NULL, 13 | `bitcoin` tinyint(1) DEFAULT NULL, 14 | `amount` bigint(26) DEFAULT '0', 15 | PRIMARY KEY (`id`), 16 | UNIQUE KEY `balance_id_uindex` (`id`), 17 | UNIQUE KEY `balance_payment_address_pool_type_bitcoin_payment_id_uindex` (`payment_address`,`pool_type`,`bitcoin`,`payment_id`), 18 | KEY `balance_payment_address_payment_id_index` (`payment_address`,`payment_id`) 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 20 | CREATE TABLE `bans` ( 21 | `id` int(11) NOT NULL AUTO_INCREMENT, 22 | `ip_address` varchar(40) DEFAULT NULL, 23 | `mining_address` varchar(200) DEFAULT NULL, 24 | `active` tinyint(1) DEFAULT '1', 25 | `ins_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 26 | PRIMARY KEY (`id`), 27 | UNIQUE KEY `bans_id_uindex` (`id`) 28 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 29 | CREATE TABLE `block_log` ( 30 | `id` int(11) NOT NULL COMMENT 'Block Height', 31 | `orphan` tinyint(1) DEFAULT '1', 32 | `hex` varchar(128) NOT NULL, 33 | `find_time` timestamp NULL DEFAULT NULL, 34 | `reward` bigint(20) DEFAULT NULL, 35 | `difficulty` bigint(20) DEFAULT NULL, 36 | `major_version` int(11) DEFAULT NULL, 37 | `minor_version` int(11) DEFAULT NULL, 38 | PRIMARY KEY (`hex`), 39 | UNIQUE KEY `block_log_hex_uindex` (`hex`) 40 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 41 | CREATE TABLE `config` ( 42 | `id` int(11) NOT NULL AUTO_INCREMENT, 43 | `module` varchar(32) DEFAULT NULL, 44 | `item` varchar(32) DEFAULT NULL, 45 | `item_value` mediumtext, 46 | `item_type` varchar(64) DEFAULT NULL, 47 | `Item_desc` varchar(512) DEFAULT NULL, 48 | PRIMARY KEY (`id`), 49 | UNIQUE KEY `config_id_uindex` (`id`), 50 | UNIQUE KEY `config_module_item_uindex` (`module`,`item`) 51 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 52 | CREATE TABLE `payments` ( 53 | `id` int(11) NOT NULL AUTO_INCREMENT, 54 | `unlocked_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 55 | `paid_time` timestamp NOT NULL DEFAULT '1970-01-01 00:00:01', 56 | `pool_type` varchar(64) DEFAULT NULL, 57 | `payment_address` varchar(125) DEFAULT NULL, 58 | `transaction_id` int(11) DEFAULT NULL COMMENT 'Transaction ID in the transactions table', 59 | `bitcoin` tinyint(1) DEFAULT '0', 60 | `amount` bigint(20) DEFAULT NULL, 61 | `block_id` int(11) DEFAULT NULL, 62 | `payment_id` varchar(128) DEFAULT NULL, 63 | `transfer_fee` bigint(20) DEFAULT '0', 64 | PRIMARY KEY (`id`), 65 | UNIQUE KEY `payments_id_uindex` (`id`), 66 | KEY `payments_transactions_id_fk` (`transaction_id`), 67 | KEY `payments_payment_address_payment_id_index` (`payment_address`,`payment_id`) 68 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 69 | CREATE TABLE `pools` ( 70 | `id` int(11) NOT NULL, 71 | `ip` varchar(72) NOT NULL, 72 | `last_checkin` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 73 | `active` tinyint(1) NOT NULL, 74 | `blockID` int(11) DEFAULT NULL, 75 | `blockIDTime` timestamp NULL DEFAULT NULL, 76 | `hostname` varchar(128) DEFAULT NULL, 77 | PRIMARY KEY (`id`), 78 | UNIQUE KEY `pools_id_uindex` (`id`) 79 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 80 | CREATE TABLE `port_config` ( 81 | `poolPort` int(11) NOT NULL, 82 | `difficulty` int(11) DEFAULT '1000', 83 | `portDesc` varchar(128) DEFAULT NULL, 84 | `portType` varchar(16) DEFAULT NULL, 85 | `hidden` tinyint(1) DEFAULT '0', 86 | `ssl` tinyint(1) DEFAULT '0', 87 | PRIMARY KEY (`poolPort`), 88 | UNIQUE KEY `port_config_poolPort_uindex` (`poolPort`) 89 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 90 | CREATE TABLE `ports` ( 91 | `pool_id` int(11) DEFAULT NULL, 92 | `network_port` int(11) DEFAULT NULL, 93 | `starting_diff` int(11) DEFAULT NULL, 94 | `port_type` varchar(64) DEFAULT NULL, 95 | `description` varchar(256) DEFAULT NULL, 96 | `hidden` tinyint(1) DEFAULT '0', 97 | `ip_address` varchar(256) DEFAULT NULL, 98 | `lastSeen` timestamp NULL DEFAULT NULL, 99 | `miners` int(11) DEFAULT NULL, 100 | `ssl_port` tinyint(1) DEFAULT '0' 101 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 102 | CREATE TABLE `shapeshiftTxn` ( 103 | `id` varchar(64) NOT NULL, 104 | `address` varchar(128) DEFAULT NULL, 105 | `paymentID` varchar(128) DEFAULT NULL, 106 | `depositType` varchar(16) DEFAULT NULL, 107 | `withdrawl` varchar(128) DEFAULT NULL, 108 | `withdrawlType` varchar(16) DEFAULT NULL, 109 | `returnAddress` varchar(128) DEFAULT NULL, 110 | `returnAddressType` varchar(16) DEFAULT NULL, 111 | `txnStatus` varchar(64) DEFAULT NULL, 112 | `amountDeposited` bigint(26) DEFAULT NULL, 113 | `amountSent` float DEFAULT NULL, 114 | `transactionHash` varchar(128) DEFAULT NULL, 115 | PRIMARY KEY (`id`), 116 | UNIQUE KEY `shapeshiftTxn_id_uindex` (`id`) 117 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 118 | CREATE TABLE `transactions` ( 119 | `id` int(11) NOT NULL AUTO_INCREMENT, 120 | `bitcoin` tinyint(1) DEFAULT NULL, 121 | `address` varchar(128) DEFAULT NULL, 122 | `payment_id` varchar(128) DEFAULT NULL, 123 | `xmr_amt` bigint(26) DEFAULT NULL, 124 | `btc_amt` bigint(26) DEFAULT NULL, 125 | `transaction_hash` varchar(128) DEFAULT NULL, 126 | `submitted_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 127 | `mixin` int(11) DEFAULT NULL, 128 | `fees` bigint(26) DEFAULT NULL, 129 | `payees` int(11) DEFAULT NULL, 130 | `exchange_rate` bigint(26) DEFAULT NULL, 131 | `confirmed` tinyint(1) DEFAULT NULL, 132 | `confirmed_time` timestamp NULL DEFAULT NULL, 133 | `exchange_name` varchar(64) DEFAULT NULL, 134 | `exchange_txn_id` varchar(128) DEFAULT NULL, 135 | PRIMARY KEY (`id`), 136 | UNIQUE KEY `transactions_id_uindex` (`id`), 137 | KEY `transactions_shapeshiftTxn_id_fk` (`exchange_txn_id`) 138 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 139 | CREATE TABLE `users` ( 140 | `id` int(11) NOT NULL AUTO_INCREMENT, 141 | `username` varchar(256) NOT NULL, 142 | `pass` varchar(64) DEFAULT NULL, 143 | `email` varchar(256) DEFAULT NULL, 144 | `admin` tinyint(1) DEFAULT '0', 145 | `payout_threshold` bigint(16) DEFAULT '0', 146 | `enable_email` tinyint(1) DEFAULT '1', 147 | PRIMARY KEY (`id`), 148 | UNIQUE KEY `users_id_uindex` (`id`), 149 | UNIQUE KEY `users_username_uindex` (`username`) 150 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 151 | CREATE TABLE `xmrtoTxn` ( 152 | `id` varchar(64) NOT NULL, 153 | `address` varchar(128) DEFAULT NULL, 154 | `paymentID` varchar(128) DEFAULT NULL, 155 | `depositType` varchar(16) DEFAULT NULL, 156 | `withdrawl` varchar(128) DEFAULT NULL, 157 | `withdrawlType` varchar(16) DEFAULT NULL, 158 | `returnAddress` varchar(128) DEFAULT NULL, 159 | `returnAddressType` varchar(16) DEFAULT NULL, 160 | `txnStatus` varchar(64) DEFAULT NULL, 161 | `amountDeposited` bigint(26) DEFAULT NULL, 162 | `amountSent` float DEFAULT NULL, 163 | `transactionHash` varchar(128) DEFAULT NULL, 164 | PRIMARY KEY (`id`), 165 | UNIQUE KEY `xmrtoTxn_id_uindex` (`id`) 166 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 167 | 168 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'minerTimeout', '900', 'int', 'Length of time before a miner is flagged inactive.'); 169 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'banEnabled', 'true', 'bool', 'Enables/disabled banning of "bad" miners.'); 170 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'banLength', '-15m', 'string', 'Ban duration except perma-bans'); 171 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'targetTime', '30', 'int', 'Time in seconds between share finds'); 172 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'trustThreshold', '30', 'int', 'Number of shares before miner trust can kick in.'); 173 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'banPercent', '25', 'int', 'Percentage of shares that need to be invalid to be banned.'); 174 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'banThreshold', '30', 'int', 'Number of shares before bans can begin'); 175 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'trustedMiners', 'true', 'bool', 'Enable the miner trust system'); 176 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'trustChange', '1', 'int', 'Change in the miner trust in percent'); 177 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'trustMin', '20', 'int', 'Minimum level of miner trust'); 178 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'trustPenalty', '30', 'int', 'Number of shares that must be successful to be trusted, reset to this value if trust share is broken'); 179 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'retargetTime', '60', 'int', 'Time between difficulty retargets'); 180 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('daemon', 'address', '127.0.0.1', 'string', 'Monero Daemon RPC IP'); 181 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('daemon', 'port', '18081', 'int', 'Monero Daemon RPC Port'); 182 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('wallet', 'address', '127.0.0.1', 'string', 'Monero Daemon RPC Wallet IP'); 183 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('wallet', 'port', '18082', 'int', 'Monero Daemon RPC Wallet Port'); 184 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('rpc', 'https', 'false', 'bool', 'Enable RPC over SSL'); 185 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'maxDifficulty', '500000', 'int', 'Maximum difficulty for VarDiff'); 186 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'minDifficulty', '100', 'int', 'Minimum difficulty for VarDiff'); 187 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'varDiffVariance', '20', 'int', 'Percentage out of the target time that difficulty changes'); 188 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'varDiffMaxChange', '125', 'int', 'Percentage amount that the difficulty may change'); 189 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'btcFee', '1.5', 'float', 'Fee charged for auto withdrawl via BTC'); 190 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'ppsFee', '6.5', 'float', 'Fee charged for usage of the PPS pool'); 191 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'pplnsFee', '.6', 'float', 'Fee charged for the usage of the PPLNS pool'); 192 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'propFee', '.7', 'float', 'Fee charged for the usage of the proportial pool'); 193 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'soloFee', '.4', 'float', 'Fee charged for usage of the solo mining pool'); 194 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'exchangeMin', '5', 'float', 'Minimum XMR balance for payout to exchange/payment ID'); 195 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'walletMin', '.3', 'float', 'Minimum XMR balance for payout to personal wallet'); 196 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'devDonation', '3', 'float', 'Donation to XMR core development'); 197 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'poolDevDonation', '3', 'float', 'Donation to pool developer'); 198 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'denom', '.000001', 'float', 'Minimum balance that will be paid out to.'); 199 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'blocksRequired', '60', 'int', 'Blocks required to validate a payout before it''s performed.'); 200 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'sigDivisor', '1000000000000', 'int', 'Divisor for turning coin into human readable amounts '); 201 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'feesForTXN', '10', 'int', 'Amount of XMR that is left from the fees to pay miner fees.'); 202 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'maxTxnValue', '250', 'int', 'Maximum amount of XMR to send in a single transaction'); 203 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'shapeshiftPair', 'xmr_btc', 'string', 'Pair to use in all shapeshift lookups for auto BTC payout'); 204 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'coinCode', 'XMR', 'string', 'Coincode to be loaded up w/ the shapeshift getcoins argument.'); 205 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'allowBitcoin', 'false', 'bool', 'Allow the pool to auto-payout to BTC via ShapeShift'); 206 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'exchangeRate', '0', 'float', 'Current exchange rate'); 207 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'bestExchange', 'xmrto', 'string', 'Current best exchange'); 208 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'mixIn', '4', 'int', 'Mixin count for coins that support such things.'); 209 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'statsBufferLength', '480', 'int', 'Number of items to be cached in the stats buffers.'); 210 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pps', 'enable', 'false', 'bool', 'Enable PPS or not'); 211 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pplns', 'shareMulti', '2', 'int', 'Multiply this times difficulty to set the N in PPLNS'); 212 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pplns', 'shareMultiLog', '3', 'int', 'How many times the difficulty of the current block do we keep in shares before clearing them out'); 213 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'blockCleaner', 'true', 'bool', 'Enable the deletion of blocks or not.'); 214 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pool', 'address', '', 'string', 'Address to mine to, this should be the wallet-rpc address.'); 215 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'feeAddress', '', 'string', 'Address that pool fees are sent to.'); 216 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'mailgunKey', '', 'string', 'MailGun API Key for notification'); 217 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'mailgunURL', '', 'string', 'MailGun URL for notifications'); 218 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'emailFrom', '', 'string', 'From address for the notification emails'); 219 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'testnet', 'false', 'bool', 'Does this pool use testnet?'); 220 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('pplns', 'enable', 'true', 'bool', 'Enable PPLNS on the pool.'); 221 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('solo', 'enable', 'true', 'bool', 'Enable SOLO mining on the pool'); 222 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'feeSlewAmount', '.011', 'float', 'Amount to charge for the txn fee'); 223 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'feeSlewEnd', '4', 'float', 'Value at which txn fee amount drops to 0'); 224 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'rpcPasswordEnabled', 'false', 'bool', 'Does the wallet use a RPC password?'); 225 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'rpcPasswordPath', '', 'string', 'Path and file for the RPC password file location'); 226 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'maxPaymentTxns', '5', 'int', 'Maximum number of transactions in a single payment'); 227 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'shareHost', '', 'string', 'Host that receives share information'); 228 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('email', 'workerNotHashingBody', 'Hello,\n\nYour worker: %(worker)s has stopped submitting hashes at: %(timestamp)s UTC\n\nThank you,\n%(poolEmailSig)s', 'string', 'Email sent to the miner when their worker stops hashing'); 229 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('email', 'workerNotHashingSubject', 'Worker %(worker)s stopped hashing', 'string', 'Subject of email sent to miner when worker stops hashing'); 230 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('general', 'emailSig', 'NodeJS-Pool Administration Team', 'string', 'Signature line for the emails.'); 231 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'timer', '120', 'int', 'Number of minutes between main payment daemon cycles'); 232 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'timerRetry', '25', 'int', 'Number of minutes between payment daemon retrying due to not enough funds'); 233 | INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('payout', 'priority', '0', 'int', 'Payout priority setting. 0 = use default (4x fee); 1 = low prio (1x fee)'); 234 | INSERT INTO pool.users (username, pass, email, admin, payout_threshold) VALUES ('Administrator', null, 'Password123', 1, 0); 235 | INSERT INTO pool.port_config (poolPort, difficulty, portDesc, portType, hidden, `ssl`) VALUES (3333, 1000, 'Low-End Hardware (Up to 30-40 h/s)', 'pplns', 0, 0); 236 | INSERT INTO pool.port_config (poolPort, difficulty, portDesc, portType, hidden, `ssl`) VALUES (5555, 5000, 'Medium-Range Hardware (Up to 160 h/s)', 'pplns', 0, 0); 237 | INSERT INTO pool.port_config (poolPort, difficulty, portDesc, portType, hidden, `ssl`) VALUES (7777, 10000, 'High-End Hardware (Anything else!)', 'pplns', 0, 0); 238 | INSERT INTO pool.port_config (poolPort, difficulty, portDesc, portType, hidden, `ssl`) VALUES (9000, 20000, 'Claymore SSL', 'pplns', 0, 1); 239 | -------------------------------------------------------------------------------- /deployment/caddyfile: -------------------------------------------------------------------------------- 1 | # Catch-all vhost 2 | :80 { 3 | root /var/www 4 | proxy /leafApi 127.0.0.1:8000 5 | proxy /api 127.0.0.1:8001 { 6 | without /api 7 | } 8 | proxy /socket.io 127.0.0.1:8001 { 9 | header_upstream Connection {>Connection} 10 | header_upstream Upgrade {>Upgrade} 11 | } 12 | header / { 13 | Access-Control-Allow-Methods "GET, POST, OPTIONS" 14 | Access-Control-Allow-Headers "Content-Type, x-access-token" 15 | } 16 | gzip 17 | mime .manifest text/cache-manifest 18 | } 19 | -------------------------------------------------------------------------------- /deployment/deploy.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "This assumes that you are doing a green-field install. If you're not, please exit in the next 15 seconds." 3 | sleep 15 4 | echo "Continuing install, this will prompt you for your password if you're not already running as root and you didn't enable passwordless sudo. Please do not run me as root!" 5 | if [[ `whoami` == "root" ]]; then 6 | echo "You ran me as root! Do not run me as root!" 7 | exit 1 8 | fi 9 | ROOT_SQL_PASS=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) 10 | CURUSER=$(whoami) 11 | sudo timedatectl set-timezone Etc/UTC 12 | sudo apt-get update 13 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade 14 | sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $ROOT_SQL_PASS" 15 | sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $ROOT_SQL_PASS" 16 | echo -e "[client]\nuser=root\npassword=$ROOT_SQL_PASS" | sudo tee /root/.my.cnf 17 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git python-virtualenv python3-virtualenv curl ntp build-essential screen cmake pkg-config libboost-all-dev libevent-dev libunbound-dev libminiupnpc-dev libunwind8-dev liblzma-dev libldns-dev libexpat1-dev libgtest-dev mysql-server lmdb-utils libzmq3-dev 18 | cd ~ 19 | git clone https://github.com/Snipa22/nodejs-pool.git # Change this depending on how the deployment goes. 20 | cd /usr/src/gtest 21 | sudo cmake . 22 | sudo make 23 | sudo mv libg* /usr/lib/ 24 | cd ~ 25 | sudo systemctl enable ntp 26 | cd /usr/local/src 27 | sudo git clone https://github.com/monero-project/monero.git 28 | cd monero 29 | sudo git checkout v0.11.1.0 30 | curl https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/monero_daemon.patch | sudo git apply -v 31 | sudo make -j$(nproc) 32 | sudo cp ~/nodejs-pool/deployment/monero.service /lib/systemd/system/ 33 | sudo useradd -m monerodaemon -d /home/monerodaemon 34 | BLOCKCHAIN_DOWNLOAD_DIR=$(sudo -u monerodaemon mktemp -d) 35 | sudo -u monerodaemon wget --limit-rate=50m -O $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw https://downloads.getmonero.org/blockchain.raw 36 | sudo -u monerodaemon /usr/local/src/monero/build/release/bin/monero-blockchain-import --input-file $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw --batch-size 20000 --database lmdb#fastest --verify off --data-dir /home/monerodaemon/.bitmonero 37 | sudo -u monerodaemon rm -rf $BLOCKCHAIN_DOWNLOAD_DIR 38 | sudo systemctl daemon-reload 39 | sudo systemctl enable monero 40 | sudo systemctl start monero 41 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash 42 | source ~/.nvm/nvm.sh 43 | nvm install v8.9.3 44 | cd ~/nodejs-pool 45 | npm install 46 | npm install -g pm2 47 | openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.pool" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 48 | mkdir ~/pool_db/ 49 | sed -r "s/(\"db_storage_path\": ).*/\1\"\/home\/$CURUSER\/pool_db\/\",/" config_example.json > config.json 50 | cd ~ 51 | git clone https://github.com/mesh0000/poolui.git 52 | cd poolui 53 | npm install 54 | ./node_modules/bower/bin/bower update 55 | ./node_modules/gulp/bin/gulp.js build 56 | cd build 57 | sudo ln -s `pwd` /var/www 58 | CADDY_DOWNLOAD_DIR=$(mktemp -d) 59 | cd $CADDY_DOWNLOAD_DIR 60 | curl -sL "https://snipanet.com/caddy.tar.gz" | tar -xz caddy init/linux-systemd/caddy.service 61 | sudo mv caddy /usr/local/bin 62 | sudo chown root:root /usr/local/bin/caddy 63 | sudo chmod 755 /usr/local/bin/caddy 64 | sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy 65 | sudo groupadd -g 33 www-data 66 | sudo useradd -g www-data --no-user-group --home-dir /var/www --no-create-home --shell /usr/sbin/nologin --system --uid 33 www-data 67 | sudo mkdir /etc/caddy 68 | sudo chown -R root:www-data /etc/caddy 69 | sudo mkdir /etc/ssl/caddy 70 | sudo chown -R www-data:root /etc/ssl/caddy 71 | sudo chmod 0770 /etc/ssl/caddy 72 | sudo cp ~/nodejs-pool/deployment/caddyfile /etc/caddy/Caddyfile 73 | sudo chown www-data:www-data /etc/caddy/Caddyfile 74 | sudo chmod 444 /etc/caddy/Caddyfile 75 | sudo sh -c "sed 's/ProtectHome=true/ProtectHome=false/' init/linux-systemd/caddy.service > /etc/systemd/system/caddy.service" 76 | sudo chown root:root /etc/systemd/system/caddy.service 77 | sudo chmod 644 /etc/systemd/system/caddy.service 78 | sudo systemctl daemon-reload 79 | sudo systemctl enable caddy.service 80 | sudo systemctl start caddy.service 81 | rm -rf $CADDY_DOWNLOAD_DIR 82 | cd ~ 83 | sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v8.9.3/bin `pwd`/.nvm/versions/node/v8.9.3/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd` 84 | cd ~/nodejs-pool 85 | sudo chown -R $CURUSER. ~/.pm2 86 | echo "Installing pm2-logrotate in the background!" 87 | pm2 install pm2-logrotate & 88 | mysql -u root --password=$ROOT_SQL_PASS < deployment/base.sql 89 | mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'authKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'Auth key sent with all Websocket frames for validation.')" 90 | mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'secKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'HMAC key for Passwords. JWT Secret Key. Changing this will invalidate all current logins.')" 91 | pm2 start init.js --name=api --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=api 92 | bash ~/nodejs-pool/deployment/install_lmdb_tools.sh 93 | cd ~/nodejs-pool/sql_sync/ 94 | env PATH=$PATH:`pwd`/.nvm/versions/node/v8.9.3/bin node sql_sync.js 95 | echo "You're setup! Please read the rest of the readme for the remainder of your setup and configuration. These steps include: Setting your Fee Address, Pool Address, Global Domain, and the Mailgun setup!" 96 | -------------------------------------------------------------------------------- /deployment/deploy_aeon.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "This assumes that you are doing a green-field install. If you're not, please exit in the next 15 seconds." 3 | sleep 15 4 | echo "Continuing install, this will prompt you for your password if you're not already running as root and you didn't enable passwordless sudo. Please do not run me as root!" 5 | if [[ `whoami` == "root" ]]; then 6 | echo "You ran me as root! Do not run me as root!" 7 | exit 1 8 | fi 9 | ROOT_SQL_PASS=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1) 10 | CURUSER=$(whoami) 11 | echo "Etc/UTC" | sudo tee -a /etc/timezone 12 | sudo rm -rf /etc/localtime 13 | sudo ln -s /usr/share/zoneinfo/Zulu /etc/localtime 14 | sudo dpkg-reconfigure -f noninteractive tzdata 15 | sudo apt-get update 16 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade 17 | sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password $ROOT_SQL_PASS" 18 | sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password $ROOT_SQL_PASS" 19 | echo -e "[client]\nuser=root\npassword=$ROOT_SQL_PASS" | sudo tee /root/.my.cnf 20 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git python-virtualenv python3-virtualenv curl ntp build-essential screen cmake pkg-config libboost-all-dev libevent-dev libunbound-dev libminiupnpc-dev libunwind8-dev liblzma-dev libldns-dev libexpat1-dev libgtest-dev mysql-server lmdb-utils libzmq3-dev 21 | cd /usr/src/gtest 22 | sudo cmake . 23 | sudo make 24 | sudo mv libg* /usr/lib/ 25 | cd ~ 26 | #sudo systemctl enable ntp 27 | #cd /usr/local/src 28 | #sudo git clone https://github.com/aeonix/aeon.git 29 | #cd aeon 30 | #sudo git checkout v0.9.14.0 31 | #sudo make -j$(nproc) 32 | #sudo cp ~/nodejs-pool/deployment/aeon.service /lib/systemd/system/ 33 | #sudo useradd -m aeondaemon -d /home/aeondaemon 34 | #sudo -u aeondaemon mkdir /home/aeondaemon/.aeon 35 | #BLOCKCHAIN_DOWNLOAD_DIR=$(sudo -u aeondaemon mktemp -d) 36 | #sudo -u aeondaemon wget --limit-rate=50m -O $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.bin http://74.208.156.45/blockchain.raw 37 | #sudo -u aeondaemon mv $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.bin /home/aeondaemon/.aeon/blockchain.bin 38 | #sudo -u aeondaemon rm -rf $BLOCKCHAIN_DOWNLOAD_DIR 39 | #sudo systemctl daemon-reload 40 | #sudo systemctl enable aeon 41 | #sudo systemctl start aeon 42 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash 43 | source ~/.nvm/nvm.sh 44 | nvm install v6.9.2 45 | cd ~/nodejs-pool 46 | npm install 47 | npm install -g pm2 48 | openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.pool" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 49 | mkdir ~/pool_db/ 50 | sed -r "s/(\"db_storage_path\": ).*/\1\"\/home\/$CURUSER\/pool_db\/\",/" config_example.json > config.json 51 | cd ~ 52 | git clone https://github.com/mesh0000/poolui.git 53 | cd poolui 54 | npm install 55 | ./node_modules/bower/bin/bower update 56 | ./node_modules/gulp/bin/gulp.js build 57 | cd build 58 | sudo ln -s `pwd` /var/www 59 | CADDY_DOWNLOAD_DIR=$(mktemp -d) 60 | cd $CADDY_DOWNLOAD_DIR 61 | curl -sL "https://snipanet.com/caddy.tar.gz" | tar -xz caddy init/linux-systemd/caddy.service 62 | sudo mv caddy /usr/local/bin 63 | sudo chown root:root /usr/local/bin/caddy 64 | sudo chmod 755 /usr/local/bin/caddy 65 | sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy 66 | sudo groupadd -g 33 www-data 67 | sudo useradd -g www-data --no-user-group --home-dir /var/www --no-create-home --shell /usr/sbin/nologin --system --uid 33 www-data 68 | sudo mkdir /etc/caddy 69 | sudo chown -R root:www-data /etc/caddy 70 | sudo mkdir /etc/ssl/caddy 71 | sudo chown -R www-data:root /etc/ssl/caddy 72 | sudo chmod 0770 /etc/ssl/caddy 73 | sudo cp ~/nodejs-pool/deployment/caddyfile /etc/caddy/Caddyfile 74 | sudo chown www-data:www-data /etc/caddy/Caddyfile 75 | sudo chmod 444 /etc/caddy/Caddyfile 76 | sudo sh -c "sed 's/ProtectHome=true/ProtectHome=false/' init/linux-systemd/caddy.service > /etc/systemd/system/caddy.service" 77 | sudo chown root:root /etc/systemd/system/caddy.service 78 | sudo chmod 644 /etc/systemd/system/caddy.service 79 | sudo systemctl daemon-reload 80 | sudo systemctl enable caddy.service 81 | sudo systemctl start caddy.service 82 | rm -rf $CADDY_DOWNLOAD_DIR 83 | cd ~ 84 | sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v6.9.2/bin `pwd`/.nvm/versions/node/v6.9.2/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd` 85 | cd ~/nodejs-pool 86 | sudo chown -R $CURUSER. ~/.pm2 87 | echo "Installing pm2-logrotate in the background!" 88 | pm2 install pm2-logrotate & 89 | mysql -u root --password=$ROOT_SQL_PASS < deployment/base.sql 90 | mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'authKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'Auth key sent with all Websocket frames for validation.')" 91 | mysql -u root --password=$ROOT_SQL_PASS pool -e "INSERT INTO pool.config (module, item, item_value, item_type, Item_desc) VALUES ('api', 'secKey', '`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1`', 'string', 'HMAC key for Passwords. JWT Secret Key. Changing this will invalidate all current logins.')" 92 | pm2 start init.js --name=api --log-date-format="YYYY-MM-DD HH:mm Z" -- --module=api 93 | bash ~/nodejs-pool/deployment/install_lmdb_tools.sh 94 | cd ~/nodejs-pool/sql_sync/ 95 | env PATH=$PATH:`pwd`/.nvm/versions/node/v6.9.2/bin node sql_sync.js 96 | echo "You're setup! Please read the rest of the readme for the remainder of your setup and configuration. These steps include: Setting your Fee Address, Pool Address, Global Domain, and the Mailgun setup!" 97 | -------------------------------------------------------------------------------- /deployment/install_lmdb_tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ~ 3 | git clone https://github.com/LMDB/lmdb 4 | cd lmdb 5 | git checkout 4d2154397afd90ca519bfa102b2aad515159bd50 6 | cd libraries/liblmdb/ 7 | make -j `nproc` 8 | mkdir ~/.bin 9 | echo ' ' >> ~/.bashrc 10 | echo 'export PATH=~/.bin:$PATH' >> ~/.bashrc 11 | for i in mdb_copy mdb_dump mdb_load mdb_stat; do cp $i ~/.bin/; done 12 | echo "Please run source ~/.bashrc to initialize the new LMDB tools. Thanks for flying Snipa22 Patch Services." -------------------------------------------------------------------------------- /deployment/leaf.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "This assumes that you are doing a green-field install. If you're not, please exit in the next 15 seconds." 3 | sleep 15 4 | echo "Continuing install, this will prompt you for your password if you're not already running as root and you didn't enable passwordless sudo. Please do not run me as root!" 5 | if [[ `whoami` == "root" ]]; then 6 | echo "You ran me as root! Do not run me as root!" 7 | exit 1 8 | fi 9 | CURUSER=$(whoami) 10 | sudo timedatectl set-timezone Etc/UTC 11 | sudo apt-get update 12 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade 13 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git python-virtualenv python3-virtualenv curl ntp build-essential screen cmake pkg-config libboost-all-dev libevent-dev libunbound-dev libminiupnpc-dev libunwind8-dev liblzma-dev libldns-dev libexpat1-dev libgtest-dev libzmq3-dev 14 | cd ~ 15 | git clone https://github.com/Snipa22/nodejs-pool.git # Change this depending on how the deployment goes. 16 | cd /usr/src/gtest 17 | sudo cmake . 18 | sudo make 19 | sudo mv libg* /usr/lib/ 20 | cd ~ 21 | sudo systemctl enable ntp 22 | cd /usr/local/src 23 | sudo git clone https://github.com/monero-project/monero.git 24 | cd monero 25 | sudo git checkout v0.11.0.0 26 | curl https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/monero_daemon.patch | sudo git apply -v 27 | sudo make -j$(nproc) 28 | sudo cp ~/nodejs-pool/deployment/monero.service /lib/systemd/system/ 29 | sudo useradd -m monerodaemon -d /home/monerodaemon 30 | BLOCKCHAIN_DOWNLOAD_DIR=$(sudo -u monerodaemon mktemp -d) 31 | sudo -u monerodaemon wget --limit-rate=50m -O $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw https://downloads.getmonero.org/blockchain.raw 32 | sudo -u monerodaemon /usr/local/src/monero/build/release/bin/monero-blockchain-import --input-file $BLOCKCHAIN_DOWNLOAD_DIR/blockchain.raw --batch-size 20000 --database lmdb#fastest --verify off --data-dir /home/monerodaemon/.bitmonero 33 | sudo -u monerodaemon rm -rf $BLOCKCHAIN_DOWNLOAD_DIR 34 | sudo systemctl daemon-reload 35 | sudo systemctl enable monero 36 | sudo systemctl start monero 37 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash 38 | source ~/.nvm/nvm.sh 39 | nvm install v8.9.3 40 | cd ~/nodejs-pool 41 | npm install 42 | npm install -g pm2 43 | openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.pool" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 44 | cd ~ 45 | sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v8.9.3/bin `pwd`/.nvm/versions/node/v8.9.3/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd` 46 | sudo chown -R $CURUSER. ~/.pm2 47 | echo "Installing pm2-logrotate in the background!" 48 | pm2 install pm2-logrotate & 49 | echo "You're setup with a leaf node! Congrats" 50 | -------------------------------------------------------------------------------- /deployment/monero.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Monero Daemon 3 | After=network.target 4 | 5 | [Service] 6 | Type=forking 7 | GuessMainPID=no 8 | ExecStart=/usr/local/src/monero/build/release/bin/monerod --rpc-bind-ip 127.0.0.1 --detach --restricted-rpc 9 | Restart=always 10 | User=monerodaemon 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /deployment/monero_daemon.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp 2 | index 4b3fa787..a2340139 100644 3 | --- a/src/cryptonote_core/tx_pool.cpp 4 | +++ b/src/cryptonote_core/tx_pool.cpp 5 | @@ -863,7 +863,7 @@ namespace cryptonote 6 | LockedTXN lock(m_blockchain); 7 | 8 | auto sorted_it = m_txs_by_fee_and_receive_time.begin(); 9 | - while (sorted_it != m_txs_by_fee_and_receive_time.end()) 10 | + while (sorted_it != m_txs_by_fee_and_receive_time.end() && bl.tx_hashes.size() <= 120) 11 | { 12 | txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(sorted_it->second); 13 | LOG_PRINT_L2("Considering " << sorted_it->second << ", size " << meta.blob_size << ", current block size " << total_size << "/" << max_total_size << ", current coinbase " << print_money(best_coinbase)); 14 | -------------------------------------------------------------------------------- /deployment/upgrade_monero.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "This assumes that you have a standard nodejs-pool install, and will patch and update it to the latest stable builds of Monero." 3 | sleep 15 4 | echo "Continuing install, this will prompt you for your password if you didn't enable passwordless sudo. Please do not run me as root!" 5 | cd /usr/local/src/monero 6 | sudo git checkout . 7 | sudo git checkout master 8 | sudo git pull 9 | sudo git checkout origin/release-v0.11.0.0 10 | curl -L https://raw.githubusercontent.com/Snipa22/nodejs-pool/master/deployment/monero_daemon.patch | sudo git apply -v 11 | sudo rm -rf build 12 | sudo make -j$(nproc) 13 | echo "Done building the new Monero daemon! Please go ahead and reboot monero with: sudo systemctl restart monero as soon as the pool source is updated!" 14 | -------------------------------------------------------------------------------- /init.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let argv = require('minimist')(process.argv.slice(2)); 5 | let config = fs.readFileSync("./config.json"); 6 | let coinConfig = fs.readFileSync("./coinConfig.json"); 7 | let protobuf = require('protocol-buffers'); 8 | let path = require('path'); 9 | 10 | global.support = require("./lib/support.js")(); 11 | global.config = JSON.parse(config); 12 | global.mysql = mysql.createPool(global.config.mysql); 13 | global.protos = protobuf(fs.readFileSync('./lib/data.proto')); 14 | global.argv = argv; 15 | let comms; 16 | let coinInc; 17 | 18 | // Config Table Layout 19 | // . 20 | 21 | global.mysql.query("SELECT * FROM config").then(function (rows) { 22 | rows.forEach(function (row){ 23 | if (!global.config.hasOwnProperty(row.module)){ 24 | global.config[row.module] = {}; 25 | } 26 | if (global.config[row.module].hasOwnProperty(row.item)){ 27 | return; 28 | } 29 | switch(row.item_type){ 30 | case 'int': 31 | global.config[row.module][row.item] = parseInt(row.item_value); 32 | break; 33 | case 'bool': 34 | global.config[row.module][row.item] = (row.item_value === "true"); 35 | break; 36 | case 'string': 37 | global.config[row.module][row.item] = row.item_value; 38 | break; 39 | case 'float': 40 | global.config[row.module][row.item] = parseFloat(row.item_value); 41 | break; 42 | } 43 | }); 44 | }).then(function(){ 45 | global.config['coin'] = JSON.parse(coinConfig)[global.config.coin]; 46 | coinInc = require(global.config.coin.funcFile); 47 | global.coinFuncs = new coinInc(); 48 | if (argv.module === 'pool'){ 49 | comms = require('./lib/remote_comms'); 50 | } else { 51 | comms = require('./lib/local_comms'); 52 | } 53 | global.database = new comms(); 54 | global.database.initEnv(); 55 | global.coinFuncs.blockedAddresses.push(global.config.pool.address); 56 | global.coinFuncs.blockedAddresses.push(global.config.payout.feeAddress); 57 | if (argv.hasOwnProperty('tool') && fs.existsSync('./tools/'+argv.tool+'.js')) { 58 | require('./tools/'+argv.tool+'.js'); 59 | } else if (argv.hasOwnProperty('module')){ 60 | switch(argv.module){ 61 | case 'pool': 62 | global.config.ports = []; 63 | global.mysql.query("SELECT * FROM port_config").then(function(rows){ 64 | rows.forEach(function(row){ 65 | row.hidden = row.hidden === 1; 66 | row.ssl = row.ssl === 1; 67 | global.config.ports.push({ 68 | port: row.poolPort, 69 | difficulty: row.difficulty, 70 | desc: row.portDesc, 71 | portType: row.portType, 72 | hidden: row.hidden, 73 | ssl: row.ssl 74 | }); 75 | }); 76 | }).then(function(){ 77 | require('./lib/pool.js'); 78 | }); 79 | break; 80 | case 'blockManager': 81 | require('./lib/blockManager.js'); 82 | break; 83 | case 'payments': 84 | require('./lib/payments.js'); 85 | break; 86 | case 'api': 87 | require('./lib/api.js'); 88 | break; 89 | case 'remoteShare': 90 | require('./lib/remoteShare.js'); 91 | break; 92 | case 'worker': 93 | require('./lib/worker.js'); 94 | break; 95 | case 'longRunner': 96 | require('./lib/longRunner.js'); 97 | break; 98 | default: 99 | console.error("Invalid module provided. Please provide a valid module"); 100 | process.exit(1); 101 | } 102 | } else { 103 | console.error("Invalid module/tool provided. Please provide a valid module/tool"); 104 | console.error("Valid Modules: pool, blockManager, payments, api, remoteShare, worker, longRunner"); 105 | let valid_tools = "Valid Tools: "; 106 | fs.readdirSync('./tools/').forEach(function(line){ 107 | valid_tools += path.parse(line).name + ", "; 108 | }); 109 | valid_tools = valid_tools.slice(0, -2); 110 | console.error(valid_tools); 111 | process.exit(1); 112 | } 113 | }); -------------------------------------------------------------------------------- /lib/blockManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const range = require("range"); 3 | const debug = require("debug")("blockManager"); 4 | const async = require("async"); 5 | 6 | // This file is for managing the block databases within the SQL database. 7 | // Primary Tasks: 8 | // Sync the chain into the block_log database. - Scan on startup for missing data, starting from block 0 9 | // Maintain a check for valid blocks in the system. (Only last number of blocks required for validation of payouts) - Perform every 2 minutes. Scan on the main blocks table as well for sanity sake. 10 | // Maintain the block_log database in order to ensure payments happen smoothly. - Scan every 1 second for a change in lastblockheader, if it changes, insert into the DB. 11 | 12 | let blockIDCache = []; 13 | let scanInProgress = false; 14 | let blockHexCache = {}; 15 | let lastBlock = 0; 16 | let balanceIDCache = {}; 17 | let blockScannerTask; 18 | let blockQueue = async.queue(function (task, callback) { 19 | // Todo: Implement within the coins/.js file. 20 | global.support.rpcDaemon('getblockheaderbyheight', {"height": task.blockID}, function (body) { 21 | let blockData = body.result.block_header; 22 | if (blockData.hash in blockHexCache) { 23 | return callback(); 24 | } 25 | debug("Adding block to block_log, ID: " + task.blockID); 26 | blockIDCache.push(task.blockID); 27 | blockHexCache[body.result.block_header.hash] = null; 28 | global.mysql.query("INSERT INTO block_log (id, orphan, hex, find_time, reward, difficulty, major_version, minor_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 29 | [task.blockID, blockData.orphan_status, blockData.hash, global.support.formatDate(blockData.timestamp * 1000), blockData.reward, blockData.difficulty, blockData.major_version, blockData.minor_version]).then(function () { 30 | return calculatePPSPayments(blockData, callback); 31 | }).catch(function (err) { 32 | debug("BlockHexCache Check: " + blockData.hash in blockHexCache); 33 | debug("BlockIDCache Check: " + blockIDCache.hasOwnProperty(task.blockID)); 34 | debug("Hex: " + blockData.hash + " Height:" + task.blockID); 35 | console.error("Tried to reprocess a block that'd already been processed"); 36 | console.error(JSON.stringify(err)); 37 | return callback(); 38 | }); 39 | }); 40 | }, 16); 41 | 42 | blockQueue.drain = function () { 43 | console.log("Scan complete, unlocking remainder of blockManager functionality."); 44 | scanInProgress = false; 45 | if (typeof(blockScannerTask) === 'undefined'){ 46 | blockScannerTask = setInterval(blockScanner, 1000); 47 | } 48 | }; 49 | 50 | let createBalanceQueue = async.queue(function (task, callback) { 51 | let pool_type = task.pool_type; 52 | let payment_address = task.payment_address; 53 | let payment_id = task.payment_id; 54 | let bitcoin = task.bitcoin; 55 | let query = "SELECT id FROM balance WHERE payment_address = ? AND payment_id is ? AND pool_type = ? AND bitcoin = ?"; 56 | if (payment_id !== null) { 57 | query = "SELECT id FROM balance WHERE payment_address = ? AND payment_id = ? AND pool_type = ? AND bitcoin = ?"; 58 | } 59 | let cacheKey = payment_address + pool_type + bitcoin + payment_id; 60 | debug("Processing a account add/check for:" + JSON.stringify(task)); 61 | global.mysql.query(query, [payment_address, payment_id, pool_type, bitcoin]).then(function (rows) { 62 | if (rows.length === 0) { 63 | global.mysql.query("INSERT INTO balance (payment_address, payment_id, pool_type, bitcoin) VALUES (?, ?, ?, ?)", [payment_address, payment_id, pool_type, bitcoin]).then(function (result) { 64 | debug("Added to the SQL database: " + result.insertId); 65 | balanceIDCache[cacheKey] = result.insertId; 66 | return callback(); 67 | }); 68 | } else { 69 | debug("Found it in MySQL: " + rows[0].id); 70 | balanceIDCache[cacheKey] = rows[0].id; 71 | return callback(); 72 | } 73 | }); 74 | }, 1); 75 | 76 | let balanceQueue = async.queue(function (task, callback) { 77 | let pool_type = task.pool_type; 78 | let payment_address = task.payment_address; 79 | let payment_id = null; 80 | if (typeof(task.payment_id) !== 'undefined' && task.payment_id !== null && task.payment_id.length > 10){ 81 | payment_id = task.payment_id; 82 | } 83 | task.payment_id = payment_id; 84 | let bitcoin = task.bitcoin; 85 | let amount = task.amount; 86 | debug("Processing balance increment task: " + JSON.stringify(task)); 87 | async.waterfall([ 88 | function (intCallback) { 89 | let cacheKey = payment_address + pool_type + bitcoin + payment_id; 90 | if (cacheKey in balanceIDCache) { 91 | return intCallback(null, balanceIDCache[cacheKey]); 92 | } else { 93 | createBalanceQueue.push(task, function () { 94 | }); 95 | async.until(function () { 96 | return cacheKey in balanceIDCache; 97 | }, function (intCallback) { 98 | createBalanceQueue.push(task, function () { 99 | return intCallback(null, balanceIDCache[cacheKey]); 100 | }); 101 | }, function () { 102 | return intCallback(null, balanceIDCache[cacheKey]); 103 | } 104 | ); 105 | } 106 | }, 107 | function (balance_id, intCallback) { 108 | debug("Made it to the point that I can update the balance for: " + balance_id + " for the amount: " + amount); 109 | global.mysql.query("UPDATE balance SET amount = amount+? WHERE id = ?", [amount, balance_id]).then(function () { 110 | return intCallback(null); 111 | }); 112 | } 113 | ], 114 | function () { 115 | return callback(); 116 | } 117 | ) 118 | ; 119 | }, 24 120 | ); 121 | 122 | function calculatePPSPayments(blockHeader, callback) { 123 | console.log("Performing PPS payout on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward)); 124 | let paymentData = {}; 125 | paymentData[global.config.payout.feeAddress] = { 126 | pool_type: 'fees', 127 | payment_address: global.config.payout.feeAddress, 128 | payment_id: null, 129 | bitcoin: 0, 130 | amount: 0 131 | }; 132 | paymentData[global.coinFuncs.coinDevAddress] = { 133 | pool_type: 'fees', 134 | payment_address: global.coinFuncs.coinDevAddress, 135 | payment_id: null, 136 | bitcoin: 0, 137 | amount: 0 138 | }; 139 | paymentData[global.coinFuncs.poolDevAddress] = { 140 | pool_type: 'fees', 141 | payment_address: global.coinFuncs.poolDevAddress, 142 | payment_id: null, 143 | bitcoin: 0, 144 | amount: 0 145 | }; 146 | let totalPayments = 0; 147 | let txn = global.database.env.beginTxn({readOnly: true}); 148 | let cursor = new global.database.lmdb.Cursor(txn, global.database.shareDB); 149 | for (let found = (cursor.goToRange(blockHeader.height) === blockHeader.height); found; found = cursor.goToNextDup()) { 150 | cursor.getCurrentBinary(function (key, data) { // jshint ignore:line 151 | let shareData; 152 | try { 153 | shareData = global.protos.Share.decode(data); 154 | } catch (e) { 155 | console.error(e); 156 | return; 157 | } 158 | let blockDiff = blockHeader.difficulty; 159 | let rewardTotal = blockHeader.reward; 160 | if (shareData.poolType === global.protos.POOLTYPE.PPS) { 161 | let userIdentifier = shareData.paymentAddress; 162 | if (shareData.paymentID) { 163 | userIdentifier = userIdentifier + "." + shareData.paymentID; 164 | } 165 | if (!(userIdentifier in paymentData)) { 166 | paymentData[userIdentifier] = { 167 | pool_type: 'pps', 168 | payment_address: shareData.paymentAddress, 169 | payment_id: shareData.paymentID, 170 | bitcoin: shareData.bitcoin, 171 | amount: 0 172 | }; 173 | } 174 | let amountToPay = Math.floor((shareData.shares / blockDiff) * rewardTotal); 175 | let feesToPay = Math.floor(amountToPay * (global.config.payout.ppsFee / 100)); 176 | if (shareData.bitcoin === true) { 177 | feesToPay += Math.floor(amountToPay * (global.config.payout.btcFee / 100)); 178 | } 179 | amountToPay -= feesToPay; 180 | paymentData[userIdentifier].amount = paymentData[userIdentifier].amount + amountToPay; 181 | let donations = 0; 182 | if(global.config.payout.devDonation > 0){ 183 | let devDonation = (feesToPay * (global.config.payout.devDonation / 100)); 184 | donations += devDonation; 185 | paymentData[global.coinFuncs.coinDevAddress].amount = paymentData[global.coinFuncs.coinDevAddress].amount + devDonation ; 186 | } 187 | if(global.config.payout.poolDevDonation > 0){ 188 | let poolDevDonation = (feesToPay * (global.config.payout.poolDevDonation / 100)); 189 | donations += poolDevDonation; 190 | paymentData[global.coinFuncs.poolDevAddress].amount = paymentData[global.coinFuncs.poolDevAddress].amount + poolDevDonation; 191 | } 192 | paymentData[global.config.payout.feeAddress].amount = paymentData[global.config.payout.feeAddress].amount + feesToPay - donations; 193 | } 194 | }); 195 | } 196 | cursor.close(); 197 | txn.abort(); 198 | Object.keys(paymentData).forEach(function (key) { 199 | balanceQueue.push(paymentData[key], function () { 200 | }); 201 | totalPayments += paymentData[key].amount; 202 | }); 203 | console.log("PPS payout cycle complete on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward) + " Block Payouts: " + global.support.coinToDecimal(totalPayments) + " Payout Percentage: " + (totalPayments / blockHeader.reward) * 100 + "%"); 204 | return callback(); 205 | } 206 | 207 | function calculatePPLNSPayments(blockHeader) { 208 | console.log("Performing PPLNS payout on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward)); 209 | let rewardTotal = blockHeader.reward; 210 | let blockCheckHeight = blockHeader.height; 211 | let totalPaid = 0; 212 | let paymentData = {}; 213 | paymentData[global.config.payout.feeAddress] = { 214 | pool_type: 'fees', 215 | payment_address: global.config.payout.feeAddress, 216 | payment_id: null, 217 | bitcoin: 0, 218 | amount: 0 219 | }; 220 | paymentData[global.coinFuncs.coinDevAddress] = { 221 | pool_type: 'fees', 222 | payment_address: global.coinFuncs.coinDevAddress, 223 | payment_id: null, 224 | bitcoin: 0, 225 | amount: 0 226 | }; 227 | paymentData[global.coinFuncs.poolDevAddress] = { 228 | pool_type: 'fees', 229 | payment_address: global.coinFuncs.poolDevAddress, 230 | payment_id: null, 231 | bitcoin: 0, 232 | amount: 0 233 | }; 234 | async.doWhilst(function (callback) { 235 | let txn = global.database.env.beginTxn({readOnly: true}); 236 | let cursor = new global.database.lmdb.Cursor(txn, global.database.shareDB); 237 | for (let found = (cursor.goToRange(blockCheckHeight) === blockCheckHeight); found; found = cursor.goToNextDup()) { 238 | cursor.getCurrentBinary(function (key, data) { // jshint ignore:line 239 | let shareData; 240 | try { 241 | shareData = global.protos.Share.decode(data); 242 | } catch (e) { 243 | console.error(e); 244 | return; 245 | } 246 | let blockDiff = blockHeader.difficulty; 247 | let rewardTotal = blockHeader.reward; 248 | if (shareData.poolType === global.protos.POOLTYPE.PPLNS) { 249 | let userIdentifier = shareData.paymentAddress; 250 | if (shareData.paymentID) { 251 | userIdentifier = userIdentifier + "." + shareData.paymentID; 252 | } 253 | if (!(userIdentifier in paymentData)) { 254 | paymentData[userIdentifier] = { 255 | pool_type: 'pplns', 256 | payment_address: shareData.paymentAddress, 257 | payment_id: shareData.paymentID, 258 | bitcoin: shareData.bitcoin, 259 | amount: 0 260 | }; 261 | } 262 | let amountToPay = Math.floor((shareData.shares / (blockDiff*global.config.pplns.shareMulti)) * rewardTotal); 263 | if (totalPaid + amountToPay > rewardTotal) { 264 | amountToPay = rewardTotal - totalPaid; 265 | } 266 | totalPaid += amountToPay; 267 | let feesToPay = Math.floor(amountToPay * (global.config.payout.pplnsFee / 100)); 268 | if (shareData.bitcoin === true) { 269 | feesToPay += Math.floor(amountToPay * (global.config.payout.btcFee / 100)); 270 | } 271 | amountToPay -= feesToPay; 272 | paymentData[userIdentifier].amount = paymentData[userIdentifier].amount + amountToPay; 273 | let donations = 0; 274 | if(global.config.payout.devDonation > 0){ 275 | let devDonation = Math.floor(feesToPay * (global.config.payout.devDonation / 100)); 276 | donations += devDonation; 277 | paymentData[global.coinFuncs.coinDevAddress].amount = paymentData[global.coinFuncs.coinDevAddress].amount + devDonation ; 278 | } 279 | if(global.config.payout.poolDevDonation > 0){ 280 | let poolDevDonation = Math.floor(feesToPay * (global.config.payout.poolDevDonation / 100)); 281 | donations += poolDevDonation; 282 | paymentData[global.coinFuncs.poolDevAddress].amount = paymentData[global.coinFuncs.poolDevAddress].amount + poolDevDonation; 283 | } 284 | paymentData[global.config.payout.feeAddress].amount = paymentData[global.config.payout.feeAddress].amount + feesToPay - donations; 285 | } 286 | }); 287 | } 288 | cursor.close(); 289 | txn.abort(); 290 | setImmediate(callback, null, totalPaid); 291 | }, function (totalPayment) { 292 | blockCheckHeight = blockCheckHeight - 1; 293 | debug("Decrementing the block chain check height to:" + blockCheckHeight); 294 | if (totalPayment >= rewardTotal) { 295 | debug("Loop 1: Total Payment: " + totalPayment + " Amount Paid: " + rewardTotal + " Amount Total: " + totalPaid); 296 | return false; 297 | } else { 298 | debug("Loop 2: Total Payment: " + totalPayment + " Amount Paid: " + rewardTotal + " Amount Total: " + totalPaid); 299 | return blockCheckHeight !== 0; 300 | } 301 | }, function (err) { 302 | let totalPayments = 0; 303 | Object.keys(paymentData).forEach(function (key) { 304 | balanceQueue.push(paymentData[key], function () { 305 | }); 306 | totalPayments += paymentData[key].amount; 307 | }); 308 | console.log("PPLNS payout cycle complete on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward) + " Block Payouts: " + global.support.coinToDecimal(totalPayments) + " Payout Percentage: " + (totalPayments / blockHeader.reward) * 100 + "%"); 309 | }); 310 | } 311 | 312 | function calculateSoloPayments(blockHeader) { 313 | console.log("Performing Solo payout on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward)); 314 | let txn = global.database.env.beginTxn({readOnly: true}); 315 | let cursor = new global.database.lmdb.Cursor(txn, global.database.shareDB); 316 | let paymentData = {}; 317 | paymentData[global.config.payout.feeAddress] = { 318 | pool_type: 'fees', 319 | payment_address: global.config.payout.feeAddress, 320 | payment_id: null, 321 | bitcoin: 0, 322 | amount: 0 323 | }; 324 | paymentData[global.coinFuncs.coinDevAddress] = { 325 | pool_type: 'fees', 326 | payment_address: global.coinFuncs.coinDevAddress, 327 | payment_id: null, 328 | bitcoin: 0, 329 | amount: 0 330 | }; 331 | paymentData[global.coinFuncs.poolDevAddress] = { 332 | pool_type: 'fees', 333 | payment_address: global.coinFuncs.poolDevAddress, 334 | payment_id: null, 335 | bitcoin: 0, 336 | amount: 0 337 | }; 338 | let totalPayments = 0; 339 | for (let found = (cursor.goToRange(blockHeader.height) === blockHeader.height); found; found = cursor.goToNextDup()) { 340 | cursor.getCurrentBinary(function (key, data) { // jshint ignore:line 341 | let shareData; 342 | try { 343 | shareData = global.protos.Share.decode(data); 344 | } catch (e) { 345 | console.error(e); 346 | return; 347 | } 348 | let rewardTotal = blockHeader.reward; 349 | if (shareData.poolType === global.protos.POOLTYPE.SOLO && shareData.foundBlock === true) { 350 | let userIdentifier = shareData.paymentAddress; 351 | if (shareData.paymentID) { 352 | userIdentifier = userIdentifier + "." + shareData.paymentID; 353 | } 354 | if (!(userIdentifier in paymentData)) { 355 | paymentData[userIdentifier] = { 356 | pool_type: 'solo', 357 | payment_address: shareData.paymentAddress, 358 | payment_id: shareData.paymentID, 359 | bitcoin: shareData.bitcoin, 360 | amount: 0 361 | }; 362 | } 363 | let feesToPay = Math.floor(rewardTotal * (global.config.payout.soloFee / 100)); 364 | if (shareData.bitcoin === true) { 365 | feesToPay += Math.floor(rewardTotal * (global.config.payout.btcFee / 100)); 366 | } 367 | rewardTotal -= feesToPay; 368 | paymentData[userIdentifier].amount = rewardTotal; 369 | let donations = 0; 370 | if(global.config.payout.devDonation > 0){ 371 | let devDonation = (feesToPay * (global.config.payout.devDonation / 100)); 372 | donations += devDonation; 373 | paymentData[global.coinFuncs.coinDevAddress].amount = paymentData[global.coinFuncs.coinDevAddress].amount + devDonation ; 374 | } 375 | if(global.config.payout.poolDevDonation > 0){ 376 | let poolDevDonation = (feesToPay * (global.config.payout.poolDevDonation / 100)); 377 | donations += poolDevDonation; 378 | paymentData[global.coinFuncs.poolDevAddress].amount = paymentData[global.coinFuncs.poolDevAddress].amount + poolDevDonation; 379 | } 380 | paymentData[global.config.payout.feeAddress].amount = feesToPay - donations; 381 | } 382 | }); 383 | } 384 | cursor.close(); 385 | txn.abort(); 386 | Object.keys(paymentData).forEach(function (key) { 387 | balanceQueue.push(paymentData[key], function () { 388 | }); 389 | totalPayments += paymentData[key].amount; 390 | }); 391 | console.log("Solo payout cycle complete on block: " + blockHeader.height + " Block Value: " + global.support.coinToDecimal(blockHeader.reward) + " Block Payouts: " + global.support.coinToDecimal(totalPayments) + " Payout Percentage: " + (totalPayments / blockHeader.reward) * 100 + "%"); 392 | } 393 | 394 | function blockUnlocker() { 395 | if (scanInProgress) { 396 | debug("Skipping block unlocker run as there's a scan in progress"); 397 | return; 398 | } 399 | debug("Running block unlocker"); 400 | let blockList = global.database.getValidLockedBlocks(); 401 | // Todo: Implement within the coins/.js file. 402 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 403 | let blockHeight = body.result.block_header.height; 404 | blockList.forEach(function (row) { 405 | // Todo: Implement within the coins/.js file. 406 | global.support.rpcDaemon('getblockheaderbyheight', {"height": row.height}, function (body) { 407 | if (body.result.block_header.hash !== row.hash) { 408 | global.database.invalidateBlock(row.height); 409 | global.mysql.query("UPDATE block_log SET orphan = true WHERE hex = ?", [row.hash]); 410 | blockIDCache.splice(blockIDCache.indexOf(body.result.block_header.height)); 411 | console.log("Invalidating block " + body.result.block_header.height + " due to being an orphan block"); 412 | } else { 413 | if (blockHeight - row.height > global.config.payout.blocksRequired) { 414 | blockPayments(row); 415 | } 416 | } 417 | }); 418 | 419 | }); 420 | }); 421 | } 422 | 423 | function blockPayments(block) { 424 | switch (block.poolType) { 425 | case global.protos.POOLTYPE.PPS: 426 | // PPS is paid out per share find per block, so this is handled in the main block-find loop. 427 | global.database.unlockBlock(block.hash); 428 | break; 429 | case global.protos.POOLTYPE.PPLNS: 430 | global.coinFuncs.getBlockHeaderByHash(block.hash, function (err, header) { 431 | if (err === null){ 432 | calculatePPLNSPayments(header); 433 | global.database.unlockBlock(block.hash); 434 | } 435 | }); 436 | break; 437 | case global.protos.POOLTYPE.SOLO: 438 | global.coinFuncs.getBlockHeaderByHash(block.hash, function (err, header) { 439 | if (err === null){ 440 | calculateSoloPayments(header); 441 | global.database.unlockBlock(block.hash); 442 | } 443 | }); 444 | break; 445 | default: 446 | console.log("Unknown payment type. FREAKOUT"); 447 | global.database.unlockBlock(block.hash); 448 | break; 449 | } 450 | } 451 | 452 | function blockScanner() { 453 | let inc_check = 0; 454 | if (scanInProgress) { 455 | debug("Skipping scan as there's one in progress."); 456 | return; 457 | } 458 | scanInProgress = true; 459 | global.coinFuncs.getLastBlockHeader(function (err, blockHeader) { 460 | if (err === null){ 461 | if (lastBlock === blockHeader.height) { 462 | debug("No new work to be performed, block header matches last block"); 463 | scanInProgress = false; 464 | return; 465 | } 466 | debug("Parsing data for new blocks"); 467 | lastBlock = blockHeader.height; 468 | range.range(0, (blockHeader.height - Math.floor(global.config.payout.blocksRequired/2))).forEach(function (blockID) { 469 | if (!blockIDCache.hasOwnProperty(blockID)) { 470 | inc_check += 1; 471 | blockQueue.push({blockID: blockID}, function (err) { 472 | debug("Completed block scan on " + blockID); 473 | if (err) { 474 | console.error("Error processing " + blockID); 475 | } 476 | }); 477 | } 478 | }); 479 | if (inc_check === 0) { 480 | debug("No new work to be performed, initial scan complete"); 481 | scanInProgress = false; 482 | blockScannerTask = setInterval(blockScanner, 1000); 483 | } 484 | } else { 485 | console.error(`Upstream error from the block daemon. Resetting scanner due to: ${JSON.stringify(blockHeader)}`); 486 | scanInProgress = false; 487 | blockScannerTask = setInterval(blockScanner, 1000); 488 | } 489 | }); 490 | } 491 | 492 | function initial_sync() { 493 | console.log("Performing boot-sync"); 494 | global.mysql.query("SELECT id, hex FROM block_log WHERE orphan = 0").then(function (rows) { 495 | let intCount = 0; 496 | rows.forEach(function (row) { 497 | intCount += 1; 498 | blockIDCache.push(row.id); 499 | blockHexCache[row.hex] = null; 500 | }); 501 | }).then(function () { 502 | // Enable block scanning for 1 seconds to update the block log. 503 | blockScanner(); 504 | // Scan every 120 seconds for invalidated blocks 505 | setInterval(blockUnlocker, 120000); 506 | blockUnlocker(); 507 | debug("Blocks loaded from SQL: " + blockIDCache.length); 508 | console.log("Boot-sync from SQL complete. Pending completion of queued jobs to get back to work."); 509 | }); 510 | } 511 | 512 | initial_sync(); 513 | -------------------------------------------------------------------------------- /lib/coins/aeon-rebase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const bignum = require('bignum'); 3 | const cnUtil = require('cryptonote-util'); 4 | const multiHashing = require('multi-hashing'); 5 | const crypto = require('crypto'); 6 | const debug = require('debug')('coinFuncs'); 7 | 8 | let hexChars = new RegExp("[0-9a-f]+"); 9 | 10 | function Coin(data){ 11 | this.isxmr = false; 12 | this.bestExchange = global.config.payout.bestExchange; 13 | this.data = data; 14 | let instanceId = crypto.randomBytes(4); 15 | this.coinDevAddress = "WmsSWgtT1JPg5e3cK41hKXSHVpKW7e47bjgiKmWZkYrhSS5LhRemNyqayaSBtAQ6517eo5PtH9wxHVmM78JDZSUu2W8PqRiNs"; // Developer Address 16 | this.poolDevAddress = "WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu"; // Snipa Address 17 | 18 | this.blockedAddresses = [ 19 | this.coinDevAddress, 20 | this.poolDevAddress, 21 | ]; 22 | 23 | this.exchangeAddresses = [ 24 | "WmtK9TQ6yd2ZWZDAkRsebc2ppzUq2Wuo9XRRjHMH2fvqM3ARVqk3styJ6AavJFcpJFPFtxRGAqGFoJMZGJ6YYzQ61TYGfpykX", // Bittrex 25 | ]; // These are addresses that MUST have a paymentID to perform logins with. 26 | 27 | this.prefix = 178; 28 | //this.intPrefix = 0x2733; 29 | 30 | if (global.config.general.testnet === true){ 31 | this.prefix = 0x0426; 32 | // this.intPrefix = 0x2C27; 33 | } 34 | 35 | this.supportsAutoExchange = false; 36 | 37 | this.niceHashDiff = 200000; 38 | 39 | this.getBlockHeaderByID = function(blockId, callback){ 40 | global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { 41 | if (body.hasOwnProperty('result')){ 42 | return callback(null, body.result.block_header); 43 | } else { 44 | console.error(JSON.stringify(body)); 45 | return callback(true, body); 46 | } 47 | }); 48 | }; 49 | 50 | this.getBlockHeaderByHash = function(blockHash, callback){ 51 | global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { 52 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 53 | return callback(null, body.result.block_header); 54 | } else { 55 | console.error(JSON.stringify(body)); 56 | return callback(true, body); 57 | } 58 | }); 59 | }; 60 | 61 | this.getLastBlockHeader = function(callback){ 62 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 63 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 64 | return callback(null, body.result.block_header); 65 | } else { 66 | console.error(JSON.stringify(body)); 67 | return callback(true, body); 68 | } 69 | }); 70 | }; 71 | 72 | this.getBlockTemplate = function(walletAddress, callback){ 73 | global.support.rpcDaemon('getblocktemplate', { 74 | reserve_size: 17, 75 | wallet_address: walletAddress 76 | }, function(body){ 77 | return callback(body); 78 | }); 79 | }; 80 | 81 | this.baseDiff = function(){ 82 | return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 83 | }; 84 | 85 | this.validateAddress = function(address){ 86 | // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. 87 | address = new Buffer(address); 88 | if (cnUtil.address_decode(address) === this.prefix){ 89 | return true; 90 | } 91 | return cnUtil.address_decode_integrated(address) === this.intPrefix; 92 | }; 93 | 94 | this.convertBlob = function(blobBuffer){ 95 | return cnUtil.convert_blob(blobBuffer); 96 | }; 97 | 98 | this.constructNewBlob = function(blockTemplate, NonceBuffer){ 99 | return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); 100 | }; 101 | 102 | this.getBlockID = function(blockBuffer){ 103 | return cnUtil.get_block_id(blockBuffer); 104 | }; 105 | 106 | this.BlockTemplate = function(template) { 107 | /* 108 | Generating a block template is a simple thing. Ask for a boatload of information, and go from there. 109 | Important things to consider. 110 | The reserved space is 13 bytes long now in the following format: 111 | Assuming that the extraNonce starts at byte 130: 112 | |130-133|134-137|138-141|142-145| 113 | |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| 114 | This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) 115 | Each with 4 billion clients. (clientNonce) 116 | While being unique to this particular pool thread (instanceId) 117 | With up to 4 billion clients (minerNonce/extraNonce) 118 | Overkill? Sure. But that's what we do here. Overkill. 119 | */ 120 | 121 | // Set this.blob equal to the BT blob that we get from upstream. 122 | this.blob = template.blocktemplate_blob; 123 | this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); 124 | // Set this.diff equal to the known diff for this block. 125 | this.difficulty = template.difficulty; 126 | // Set this.height equal to the known height for this block. 127 | this.height = template.height; 128 | // Set this.reserveOffset to the byte location of the reserved offset. 129 | this.reserveOffset = template.reserved_offset; 130 | // Set this.buffer to the binary decoded version of the BT blob. 131 | this.buffer = new Buffer(this.blob, 'hex'); 132 | // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. 133 | instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); 134 | // Generate a clean, shiny new buffer. 135 | this.previous_hash = new Buffer(32); 136 | // Copy in bytes 7 through 39 to this.previous_hash from the current BT. 137 | this.buffer.copy(this.previous_hash, 0, 7, 39); 138 | // Reset the Nonce. - This is the per-miner/pool nonce 139 | this.extraNonce = 0; 140 | // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. 141 | this.clientNonceLocation = this.reserveOffset + 12; 142 | // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. 143 | this.clientPoolLocation = this.reserveOffset + 8; 144 | this.nextBlob = function () { 145 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 146 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 147 | // Convert the blob into something hashable. 148 | return global.coinFuncs.convertBlob(this.buffer).toString('hex'); 149 | }; 150 | // Make it so you can get the raw block blob out. 151 | this.nextBlobWithChildNonce = function () { 152 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 153 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 154 | // Don't convert the blob to something hashable. You bad. 155 | return this.buffer.toString('hex'); 156 | }; 157 | }; 158 | 159 | this.cryptoNight = function(convertedBlob) { 160 | return multiHashing.cryptonight_light(convertedBlob, convertedBlob[0] >= 7 ? convertedBlob[0] - 6 : 0); 161 | } 162 | 163 | } 164 | 165 | module.exports = Coin; 166 | -------------------------------------------------------------------------------- /lib/coins/aeon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const bignum = require('bignum'); 3 | const cnUtil = require('cryptonote-util'); 4 | const multiHashing = require('multi-hashing'); 5 | const crypto = require('crypto'); 6 | const debug = require('debug')('coinFuncs'); 7 | 8 | let hexChars = new RegExp("[0-9a-f]+"); 9 | 10 | function Coin(data){ 11 | this.isxmr = false; 12 | this.bestExchange = global.config.payout.bestExchange; 13 | this.data = data; 14 | let instanceId = crypto.randomBytes(4); 15 | this.coinDevAddress = "WmsSWgtT1JPg5e3cK41hKXSHVpKW7e47bjgiKmWZkYrhSS5LhRemNyqayaSBtAQ6517eo5PtH9wxHVmM78JDZSUu2W8PqRiNs"; // Developer Address 16 | this.poolDevAddress = "WmtvM6SoYya4qzkoPB4wX7FACWcXyFPWAYzfz7CADECgKyBemAeb3dVb3QomHjRWwGS3VYzMJAnBXfUx5CfGLFZd1U7ssdXTu"; // Snipa Address 17 | 18 | this.blockedAddresses = [ 19 | this.coinDevAddress, 20 | this.poolDevAddress, 21 | ]; 22 | 23 | this.exchangeAddresses = [ 24 | ]; // These are addresses that MUST have a paymentID to perform logins with. 25 | 26 | this.prefix = 178; 27 | 28 | this.supportsAutoExchange = false; 29 | 30 | this.niceHashDiff = 400000; 31 | 32 | this.getBlockHeaderByID = function(blockId, callback){ 33 | global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { 34 | if (body.hasOwnProperty('result')){ 35 | return callback(null, body.result.block_header); 36 | } else { 37 | console.error(JSON.stringify(body)); 38 | return callback(true, body); 39 | } 40 | }); 41 | }; 42 | 43 | this.getBlockHeaderByHash = function(blockHash, callback){ 44 | global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { 45 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 46 | return callback(null, body.result.block_header); 47 | } else { 48 | console.error(JSON.stringify(body)); 49 | return callback(true, body); 50 | } 51 | }); 52 | }; 53 | 54 | this.getLastBlockHeader = function(callback){ 55 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 56 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 57 | return callback(null, body.result.block_header); 58 | } else { 59 | console.error(JSON.stringify(body)); 60 | return callback(true, body); 61 | } 62 | }); 63 | }; 64 | 65 | this.getBlockTemplate = function(walletAddress, callback){ 66 | global.support.rpcDaemon('getblocktemplate', { 67 | reserve_size: 17, 68 | wallet_address: walletAddress 69 | }, function(body){ 70 | return callback(body); 71 | }); 72 | }; 73 | 74 | this.baseDiff = function(){ 75 | return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 76 | }; 77 | 78 | this.validateAddress = function(address){ 79 | // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. 80 | address = new Buffer(address); 81 | if (cnUtil.address_decode(address) === this.prefix){ 82 | return true; 83 | } 84 | return cnUtil.address_decode_integrated(address) === this.intPrefix; 85 | }; 86 | 87 | this.convertBlob = function(blobBuffer){ 88 | return cnUtil.convert_blob(blobBuffer); 89 | }; 90 | 91 | this.constructNewBlob = function(blockTemplate, NonceBuffer){ 92 | return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); 93 | }; 94 | 95 | this.getBlockID = function(blockBuffer){ 96 | return cnUtil.get_block_id(blockBuffer); 97 | }; 98 | 99 | this.BlockTemplate = function(template) { 100 | /* 101 | Generating a block template is a simple thing. Ask for a boatload of information, and go from there. 102 | Important things to consider. 103 | The reserved space is 13 bytes long now in the following format: 104 | Assuming that the extraNonce starts at byte 130: 105 | |130-133|134-137|138-141|142-145| 106 | |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| 107 | This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) 108 | Each with 4 billion clients. (clientNonce) 109 | While being unique to this particular pool thread (instanceId) 110 | With up to 4 billion clients (minerNonce/extraNonce) 111 | Overkill? Sure. But that's what we do here. Overkill. 112 | */ 113 | 114 | // Set this.blob equal to the BT blob that we get from upstream. 115 | this.blob = template.blocktemplate_blob; 116 | this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); 117 | // Set this.diff equal to the known diff for this block. 118 | this.difficulty = template.difficulty; 119 | // Set this.height equal to the known height for this block. 120 | this.height = template.height; 121 | // Set this.reserveOffset to the byte location of the reserved offset. 122 | this.reserveOffset = template.reserved_offset; 123 | // Set this.buffer to the binary decoded version of the BT blob. 124 | this.buffer = new Buffer(this.blob, 'hex'); 125 | // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. 126 | instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); 127 | // Generate a clean, shiny new buffer. 128 | this.previous_hash = new Buffer(32); 129 | // Copy in bytes 7 through 39 to this.previous_hash from the current BT. 130 | this.buffer.copy(this.previous_hash, 0, 7, 39); 131 | // Reset the Nonce. - This is the per-miner/pool nonce 132 | this.extraNonce = 0; 133 | // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. 134 | this.clientNonceLocation = this.reserveOffset + 12; 135 | // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. 136 | this.clientPoolLocation = this.reserveOffset + 8; 137 | this.nextBlob = function () { 138 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 139 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 140 | // Convert the blob into something hashable. 141 | return global.coinFuncs.convertBlob(this.buffer).toString('hex'); 142 | }; 143 | // Make it so you can get the raw block blob out. 144 | this.nextBlobWithChildNonce = function () { 145 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 146 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 147 | // Don't convert the blob to something hashable. You bad. 148 | return this.buffer.toString('hex'); 149 | }; 150 | }; 151 | 152 | this.cryptoNight = multiHashing.cryptonight_light; 153 | 154 | } 155 | 156 | module.exports = Coin; 157 | -------------------------------------------------------------------------------- /lib/coins/krb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const bignum = require('bignum'); 3 | const cnUtil = require('cryptonote-util'); 4 | const multiHashing = require('multi-hashing'); 5 | const crypto = require('crypto'); 6 | const debug = require('debug')('coinFuncs'); 7 | 8 | let hexChars = new RegExp("[0-9a-f]+"); 9 | 10 | function Coin(data){ 11 | this.isxmr = false; 12 | this.bestExchange = global.config.payout.bestExchange; 13 | this.data = data; 14 | let instanceId = crypto.randomBytes(4); 15 | this.coinDevAddress = "Kdev1L9V5ow3cdKNqDpLcFFxZCqu5W2GE9xMKewsB2pUXWxcXvJaUWHcSrHuZw91eYfQFzRtGfTemReSSMN4kE445i6Etb3"; // Developer Address 16 | this.poolDevAddress = "KgseWakG2bMXHGJSsAUfzL1HykCyvD4m8gd9qgcuyZ1ufy8PqUCKRxEfAv3nahfdTrCjZByiWoCiRiohxq4u2rf2RgQ1pcJ"; // Snipa Address 17 | 18 | this.blockedAddresses = [ 19 | this.coinDevAddress, 20 | this.poolDevAddress, 21 | ]; 22 | 23 | this.exchangeAddresses = [ 24 | ]; // These are addresses that MUST have a paymentID to perform logins with. 25 | 26 | this.prefix = 111; 27 | 28 | this.supportsAutoExchange = false; 29 | 30 | this.niceHashDiff = 200000; 31 | 32 | this.getBlockHeaderByID = function(blockId, callback){ 33 | global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { 34 | if (body.hasOwnProperty('result')){ 35 | return callback(null, body.result.block_header); 36 | } else { 37 | console.error(JSON.stringify(body)); 38 | return callback(true, body); 39 | } 40 | }); 41 | }; 42 | 43 | this.getBlockHeaderByHash = function(blockHash, callback){ 44 | global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { 45 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 46 | return callback(null, body.result.block_header); 47 | } else { 48 | console.error(JSON.stringify(body)); 49 | return callback(true, body); 50 | } 51 | }); 52 | }; 53 | 54 | this.getLastBlockHeader = function(callback){ 55 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 56 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 57 | return callback(null, body.result.block_header); 58 | } else { 59 | console.error(JSON.stringify(body)); 60 | return callback(true, body); 61 | } 62 | }); 63 | }; 64 | 65 | this.getBlockTemplate = function(walletAddress, callback){ 66 | global.support.rpcDaemon('getblocktemplate', { 67 | reserve_size: 17, 68 | wallet_address: walletAddress 69 | }, function(body){ 70 | return callback(body); 71 | }); 72 | }; 73 | 74 | this.baseDiff = function(){ 75 | return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 76 | }; 77 | 78 | this.validateAddress = function(address){ 79 | // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. 80 | address = new Buffer(address); 81 | if (cnUtil.address_decode(address) === this.prefix){ 82 | return true; 83 | } 84 | return cnUtil.address_decode_integrated(address) === this.intPrefix; 85 | }; 86 | 87 | this.convertBlob = function(blobBuffer){ 88 | return cnUtil.convert_blob(blobBuffer); 89 | }; 90 | 91 | this.constructNewBlob = function(blockTemplate, NonceBuffer){ 92 | return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); 93 | }; 94 | 95 | this.getBlockID = function(blockBuffer){ 96 | return cnUtil.get_block_id(blockBuffer); 97 | }; 98 | 99 | this.BlockTemplate = function(template) { 100 | /* 101 | Generating a block template is a simple thing. Ask for a boatload of information, and go from there. 102 | Important things to consider. 103 | The reserved space is 13 bytes long now in the following format: 104 | Assuming that the extraNonce starts at byte 130: 105 | |130-133|134-137|138-141|142-145| 106 | |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| 107 | This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) 108 | Each with 4 billion clients. (clientNonce) 109 | While being unique to this particular pool thread (instanceId) 110 | With up to 4 billion clients (minerNonce/extraNonce) 111 | Overkill? Sure. But that's what we do here. Overkill. 112 | */ 113 | 114 | // Set this.blob equal to the BT blob that we get from upstream. 115 | this.blob = template.blocktemplate_blob; 116 | this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); 117 | // Set this.diff equal to the known diff for this block. 118 | this.difficulty = template.difficulty; 119 | // Set this.height equal to the known height for this block. 120 | this.height = template.height; 121 | // Set this.reserveOffset to the byte location of the reserved offset. 122 | this.reserveOffset = template.reserved_offset; 123 | // Set this.buffer to the binary decoded version of the BT blob. 124 | this.buffer = new Buffer(this.blob, 'hex'); 125 | // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. 126 | instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); 127 | // Generate a clean, shiny new buffer. 128 | this.previous_hash = new Buffer(32); 129 | // Copy in bytes 7 through 39 to this.previous_hash from the current BT. 130 | this.buffer.copy(this.previous_hash, 0, 7, 39); 131 | // Reset the Nonce. - This is the per-miner/pool nonce 132 | this.extraNonce = 0; 133 | // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. 134 | this.clientNonceLocation = this.reserveOffset + 12; 135 | // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. 136 | this.clientPoolLocation = this.reserveOffset + 8; 137 | this.nextBlob = function () { 138 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 139 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 140 | // Convert the blob into something hashable. 141 | return global.coinFuncs.convertBlob(this.buffer).toString('hex'); 142 | }; 143 | // Make it so you can get the raw block blob out. 144 | this.nextBlobWithChildNonce = function () { 145 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 146 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 147 | // Don't convert the blob to something hashable. You bad. 148 | return this.buffer.toString('hex'); 149 | }; 150 | }; 151 | 152 | this.cryptoNight = multiHashing.cryptonight; 153 | 154 | } 155 | 156 | module.exports = Coin; 157 | -------------------------------------------------------------------------------- /lib/coins/xmr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const bignum = require('bignum'); 3 | const cnUtil = require('cryptonote-util'); 4 | const multiHashing = require("cryptonight-hashing"); 5 | const crypto = require('crypto'); 6 | const debug = require('debug')('coinFuncs'); 7 | 8 | let hexChars = new RegExp("[0-9a-f]+"); 9 | 10 | function Coin(data){ 11 | this.isxmr = true; 12 | this.bestExchange = global.config.payout.bestExchange; 13 | this.data = data; 14 | let instanceId = crypto.randomBytes(4); 15 | this.coinDevAddress = "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A"; // Developer Address 16 | this.poolDevAddress = "44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr"; // Snipa Address 17 | 18 | this.blockedAddresses = [ 19 | this.coinDevAddress, 20 | this.poolDevAddress, 21 | "43SLUTpyTgXCNXsL43uD8FWZ5wLAdX7Ak67BgGp7dxnGhLmrffDTXoeGm2GBRm8JjigN9PTg2gnShQn5gkgE1JGWJr4gsEU", // Wolf0's address 22 | "42QWoLF7pdwMcTXDviJvNkWEHJ4TXnMBh2Cx6HNkVAW57E48Zfw6wLwDUYFDYJAqY7PLJUTz9cHWB5C4wUA7UJPu5wPf4sZ", // Wolf0's address 23 | "46gq64YYgCk88LxAadXbKLeQtCJtsLSD63NiEc3XHLz8NyPAyobACP161JbgyH2SgTau3aPUsFAYyK2RX4dHQoaN1ats6iT", // Claymore's Fee Address. 24 | "47mr7jYTroxQMwdKoPQuJoc9Vs9S9qCUAL6Ek4qyNFWJdqgBZRn4RYY2QjQfqEMJZVWPscupSgaqmUn1dpdUTC4fQsu3yjN" // Claymore's _other_ fee address. 25 | ]; 26 | 27 | this.exchangeAddresses = [ 28 | "46yzCCD3Mza9tRj7aqPSaxVbbePtuAeKzf8Ky2eRtcXGcEgCg1iTBio6N4sPmznfgGEUGDoBz5CLxZ2XPTyZu1yoCAG7zt6", // Shapeshift.io 29 | "463tWEBn5XZJSxLU6uLQnQ2iY9xuNcDbjLSjkn3XAXHCbLrTTErJrBWYgHJQyrCwkNgYvyV3z8zctJLPCZy24jvb3NiTcTJ", // Bittrex 30 | "44TVPcCSHebEQp4LnapPkhb2pondb2Ed7GJJLc6TkKwtSyumUnQ6QzkCCkojZycH2MRfLcujCM7QR1gdnRULRraV4UpB5n4", // Xmr.to 31 | "47sghzufGhJJDQEbScMCwVBimTuq6L5JiRixD8VeGbpjCTA12noXmi4ZyBZLc99e66NtnKff34fHsGRoyZk3ES1s1V4QVcB" // Poloniex 32 | ]; // These are addresses that MUST have a paymentID to perform logins with. 33 | 34 | this.prefix = 18; 35 | this.intPrefix = 19; 36 | this.subPrefix = 42; 37 | 38 | if (global.config.general.testnet === true) { 39 | this.prefix = 53; 40 | this.intPrefix = 54; 41 | this.subPrefix = 63; 42 | } 43 | this.supportsAutoExchange = true; 44 | 45 | this.niceHashDiff = 400000; 46 | 47 | this.getBlockHeaderByID = function(blockId, callback){ 48 | global.support.rpcDaemon('getblockheaderbyheight', {"height": blockId}, function (body) { 49 | if (body.hasOwnProperty('result')){ 50 | return callback(null, body.result.block_header); 51 | } else { 52 | console.error(JSON.stringify(body)); 53 | return callback(true, body); 54 | } 55 | }); 56 | }; 57 | 58 | this.getBlockHeaderByHash = function(blockHash, callback){ 59 | global.support.rpcDaemon('getblockheaderbyhash', {"hash": blockHash}, function (body) { 60 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 61 | return callback(null, body.result.block_header); 62 | } else { 63 | console.error(JSON.stringify(body)); 64 | return callback(true, body); 65 | } 66 | }); 67 | }; 68 | 69 | this.getLastBlockHeader = function(callback){ 70 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 71 | if (typeof(body) !== 'undefined' && body.hasOwnProperty('result')){ 72 | return callback(null, body.result.block_header); 73 | } else { 74 | console.error(JSON.stringify(body)); 75 | return callback(true, body); 76 | } 77 | }); 78 | }; 79 | 80 | this.getBlockTemplate = function(walletAddress, callback){ 81 | global.support.rpcDaemon('getblocktemplate', { 82 | reserve_size: 17, 83 | wallet_address: walletAddress 84 | }, function(body){ 85 | return callback(body); 86 | }); 87 | }; 88 | 89 | this.baseDiff = function(){ 90 | return bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 91 | }; 92 | 93 | this.validateAddress = function (address) { 94 | // This function should be able to be called from the async library, as we need to BLOCK ever so slightly to verify the address. 95 | address = new Buffer(address); 96 | let decoded_address = cnUtil.address_decode(address); 97 | if (decoded_address === this.prefix || decoded_address === this.subPrefix) { 98 | return true; 99 | } 100 | return cnUtil.address_decode_integrated(address) === this.intPrefix; 101 | }; 102 | 103 | this.convertBlob = function(blobBuffer){ 104 | return cnUtil.convert_blob(blobBuffer); 105 | }; 106 | 107 | this.constructNewBlob = function(blockTemplate, NonceBuffer){ 108 | return cnUtil.construct_block_blob(blockTemplate, NonceBuffer); 109 | }; 110 | 111 | this.getBlockID = function(blockBuffer){ 112 | return cnUtil.get_block_id(blockBuffer); 113 | }; 114 | 115 | this.BlockTemplate = function(template) { 116 | /* 117 | Generating a block template is a simple thing. Ask for a boatload of information, and go from there. 118 | Important things to consider. 119 | The reserved space is 13 bytes long now in the following format: 120 | Assuming that the extraNonce starts at byte 130: 121 | |130-133|134-137|138-141|142-145| 122 | |minerNonce/extraNonce - 4 bytes|instanceId - 4 bytes|clientPoolNonce - 4 bytes|clientNonce - 4 bytes| 123 | This is designed to allow a single block template to be used on up to 4 billion poolSlaves (clientPoolNonce) 124 | Each with 4 billion clients. (clientNonce) 125 | While being unique to this particular pool thread (instanceId) 126 | With up to 4 billion clients (minerNonce/extraNonce) 127 | Overkill? Sure. But that's what we do here. Overkill. 128 | */ 129 | 130 | // Set this.blob equal to the BT blob that we get from upstream. 131 | this.blob = template.blocktemplate_blob; 132 | this.idHash = crypto.createHash('md5').update(template.blocktemplate_blob).digest('hex'); 133 | // Set this.diff equal to the known diff for this block. 134 | this.difficulty = template.difficulty; 135 | // Set this.height equal to the known height for this block. 136 | this.height = template.height; 137 | // Set this.reserveOffset to the byte location of the reserved offset. 138 | this.reserveOffset = template.reserved_offset; 139 | // Set this.buffer to the binary decoded version of the BT blob. 140 | this.buffer = new Buffer(this.blob, 'hex'); 141 | // Copy the Instance ID to the reserve offset + 4 bytes deeper. Copy in 4 bytes. 142 | instanceId.copy(this.buffer, this.reserveOffset + 4, 0, 3); 143 | // Generate a clean, shiny new buffer. 144 | this.previous_hash = new Buffer(32); 145 | // Copy in bytes 7 through 39 to this.previous_hash from the current BT. 146 | this.buffer.copy(this.previous_hash, 0, 7, 39); 147 | // Reset the Nonce. - This is the per-miner/pool nonce 148 | this.extraNonce = 0; 149 | // The clientNonceLocation is the location at which the client pools should set the nonces for each of their clients. 150 | this.clientNonceLocation = this.reserveOffset + 12; 151 | // The clientPoolLocation is for multi-thread/multi-server pools to handle the nonce for each of their tiers. 152 | this.clientPoolLocation = this.reserveOffset + 8; 153 | if (template.seed_hash) { 154 | this.seedHash = Buffer.from(template.seed_hash, 'hex'); 155 | } else { 156 | this.seedHash = Buffer.from('00', 'hex'); 157 | } 158 | this.nextBlob = function () { 159 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 160 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 161 | // Convert the blob into something hashable. 162 | return global.coinFuncs.convertBlob(this.buffer).toString('hex'); 163 | }; 164 | // Make it so you can get the raw block blob out. 165 | this.nextBlobWithChildNonce = function () { 166 | // Write a 32 bit integer, big-endian style to the 0 byte of the reserve offset. 167 | this.buffer.writeUInt32BE(++this.extraNonce, this.reserveOffset); 168 | // Don't convert the blob to something hashable. You bad. 169 | return this.buffer.toString('hex'); 170 | }; 171 | }; 172 | 173 | this.cryptoNight = function(convertedBlob) { 174 | return multiHashing.cryptonight(convertedBlob, convertedBlob[0] >= 8 ? 8 : 1); 175 | } 176 | } 177 | 178 | module.exports = Coin; 179 | -------------------------------------------------------------------------------- /lib/data.proto: -------------------------------------------------------------------------------- 1 | enum POOLTYPE { 2 | PPLNS = 0; 3 | PPS = 1; 4 | PROP = 2; 5 | SOLO = 3; 6 | } 7 | 8 | enum MESSAGETYPE { 9 | SHARE = 0; 10 | BLOCK = 1; 11 | INVALIDSHARE = 2; 12 | } 13 | 14 | message WSData { 15 | required MESSAGETYPE msgType = 1; 16 | required string key = 2; 17 | required bytes msg = 3; 18 | required int32 exInt = 4; 19 | } 20 | 21 | message InvalidShare{ 22 | required string paymentAddress = 1; 23 | optional string paymentID = 2; 24 | required string identifier = 3; 25 | } 26 | 27 | message Share { 28 | required int32 shares = 1; 29 | required string paymentAddress = 2; 30 | required bool foundBlock = 3; 31 | optional string paymentID = 4; 32 | required bool trustedShare = 5; 33 | required POOLTYPE poolType = 6; 34 | required int32 poolID = 7; 35 | required int64 blockDiff = 8; 36 | required bool bitcoin = 9; 37 | required int32 blockHeight = 10; 38 | required int64 timestamp = 11; 39 | required string identifier = 12; 40 | } 41 | 42 | message Block { 43 | required string hash = 1; 44 | required int64 difficulty = 2; 45 | required int64 shares = 3; 46 | required int64 timestamp = 4; 47 | required POOLTYPE poolType = 5; 48 | required bool unlocked = 6; 49 | required bool valid = 7; 50 | optional int64 value = 8; 51 | } -------------------------------------------------------------------------------- /lib/longRunner.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | console.log("Cleaning up the share DB"); 4 | global.database.cleanShareDB(); 5 | console.log("Done cleaning up the shareDB"); 6 | setInterval(function(){ 7 | console.log("Cleaning up the share DB"); 8 | global.database.cleanShareDB(); 9 | console.log("Done cleaning up the shareDB"); 10 | }, 3600000); -------------------------------------------------------------------------------- /lib/payment_systems/aeon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("async"); 3 | const debug = require("debug")("payments"); 4 | 5 | let hexChars = new RegExp("[0-9a-f]+"); 6 | let extraPaymentRound = false; 7 | let paymentTimer = null; 8 | 9 | let paymentQueue = async.queue(function (paymentDetails, callback) { 10 | if (paymentTimer !== null){ 11 | clearInterval(paymentTimer); 12 | paymentTimer = null; 13 | } 14 | debug("Making payment based on: " + JSON.stringify(paymentDetails)); 15 | let transferFunc = 'transfer'; 16 | global.support.rpcWallet(transferFunc, paymentDetails, function (body) { 17 | debug("Payment made: " + JSON.stringify(body)); 18 | if (body.hasOwnProperty('error')) { 19 | if (body.error.message === "not enough money"){ 20 | console.error("Issue making payments, not enough money, will try later"); 21 | if(!extraPaymentRound){ 22 | setTimeout(function(){ 23 | makePayments(); 24 | }, global.config.payout.timerRetry * 60 * 1000); 25 | } 26 | extraPaymentRound = true; 27 | return callback(false); 28 | } else { 29 | console.error("Issue making payments" + JSON.stringify(body.error)); 30 | console.error("Will not make more payments until the payment daemon is restarted!"); 31 | //toAddress, subject, body 32 | global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", 33 | "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + 34 | ". Please investigate and restart the payment daemon as appropriate"); 35 | return; 36 | } 37 | } 38 | if (paymentDetails.hasOwnProperty('payment_id')) { 39 | console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); 40 | return callback(body.result); 41 | } else { 42 | if (transferFunc === 'transfer') { 43 | console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); 44 | } 45 | let intCount = 0; 46 | paymentDetails.destinations.forEach(function (details) { 47 | console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); 48 | intCount += 1; 49 | if (intCount === paymentDetails.destinations.length) { 50 | return callback(body.result); 51 | } 52 | }); 53 | } 54 | }); 55 | }, 1); 56 | 57 | paymentQueue.drain = function(){ 58 | extraPaymentRound = false; 59 | if (global.config.payout.timer > 35791){ 60 | console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); 61 | } else { 62 | paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); 63 | } 64 | global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); 65 | }; 66 | 67 | function Payee(amount, address, paymentID, bitcoin) { 68 | this.amount = amount; 69 | this.address = address; 70 | this.paymentID = paymentID; 71 | this.bitcoin = bitcoin; 72 | this.blockID = 0; 73 | this.poolType = ''; 74 | this.transactionID = 0; 75 | this.sqlID = 0; 76 | if (paymentID === null) { 77 | this.id = address; 78 | } else { 79 | this.id = address + "." + paymentID; 80 | } 81 | this.fee = 0; 82 | this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); 83 | this.setFeeAmount = function () { 84 | if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { 85 | this.fee = this.baseFee; 86 | } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { 87 | let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); 88 | this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); 89 | } 90 | this.fee = Math.floor(this.fee); 91 | }; 92 | 93 | this.makePaymentWithID = function () { 94 | let paymentDetails = { 95 | destinations: [ 96 | { 97 | amount: this.amount - this.fee, 98 | address: this.address 99 | } 100 | ], 101 | fee: global.config.payout.fee, 102 | unlock_time: global.config.payout.unlock_time, 103 | mixin: global.config.payout.mixIn, 104 | payment_id: this.paymentID 105 | }; 106 | let identifier = this.id; 107 | let amount = this.amount; 108 | let address = this.address; 109 | let paymentID = this.paymentID; 110 | let payee = this; 111 | debug("Payment Details: " + JSON.stringify(paymentDetails)); 112 | paymentQueue.push(paymentDetails, function (body) { 113 | if (typeof body.tx_hash !== 'undefined') { 114 | debug("Successful payment sent to: " + identifier); 115 | global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 116 | [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) { 117 | payee.transactionID = result.insertId; 118 | payee.trackPayment(); 119 | }); 120 | } else { 121 | console.error("Unknown error from the wallet."); 122 | } 123 | }); 124 | }; 125 | 126 | this.makePaymentAsIntegrated = function () { 127 | let paymentDetails = { 128 | destinations: [ 129 | { 130 | amount: this.amount - this.fee, 131 | address: this.address 132 | } 133 | ], 134 | fee: global.config.payout.fee, 135 | unlock_time: global.config.payout.unlock_time, 136 | mixin: global.config.payout.mixIn 137 | }; 138 | let identifier = this.id; 139 | let amount = this.amount; 140 | let address = this.address; 141 | let payee = this; 142 | 143 | debug("Payment Details: " + JSON.stringify(paymentDetails)); 144 | paymentQueue.push(paymentDetails, function (body) { 145 | if (typeof body.tx_hash !== 'undefined') { 146 | debug("Successful payment sent to: " + identifier); 147 | global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", 148 | [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, 1]).then(function (result) { 149 | payee.transactionID = result.insertId; 150 | payee.trackPayment(); 151 | }); 152 | } else { 153 | console.error("Unknown error from the wallet."); 154 | } 155 | }); 156 | }; 157 | 158 | this.trackPayment = function () { 159 | global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); 160 | global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + 161 | " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); 162 | }; 163 | } 164 | 165 | function makePayments() { 166 | global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { 167 | console.log("Loaded all payees into the system for processing"); 168 | let paymentDestinations = []; 169 | let totalAmount = 0; 170 | let roundCount = 0; 171 | let payeeList = []; 172 | let payeeObjects = {}; 173 | rows.forEach(function (row) { 174 | debug("Starting round for: " + JSON.stringify(row)); 175 | let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); 176 | payeeObjects[row.payment_address] = payee; 177 | global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { 178 | roundCount += 1; 179 | let threshold = 0; 180 | if (userRow.length !== 0) { 181 | threshold = userRow[0].payout_threshold; 182 | } 183 | payee.poolType = row.pool_type; 184 | payee.sqlID = row.id; 185 | if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { 186 | debug("This is the fee address internal check for value"); 187 | payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); 188 | } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { 189 | debug("Unable to pay fee address."); 190 | payee.amount = 0; 191 | } 192 | let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); 193 | if (remainder !== 0) { 194 | payee.amount -= remainder; 195 | } 196 | if (payee.amount > threshold) { 197 | payee.setFeeAmount(); 198 | if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { 199 | debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); 200 | paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); 201 | totalAmount += payee.amount; 202 | payeeList.push(payee); 203 | } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { 204 | debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); 205 | payee.makePaymentWithID(); 206 | } 207 | } 208 | debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); 209 | if (roundCount === rows.length && paymentDestinations.length > 0) { 210 | while (paymentDestinations.length > 0) { 211 | let paymentDetails = { 212 | destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), 213 | mixin: global.config.payout.mixIn, 214 | fee: global.config.payout.fee, 215 | unlock_time: global.config.payout.unlock_time 216 | }; 217 | console.log("Paying out: " + paymentDetails.destinations.length + " people"); 218 | paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line 219 | // This is the only section that could potentially contain multiple txns. Lets do this safely eh? 220 | if (typeof body.tx_hash !== 'undefined') { 221 | debug("Made it to the SQL insert for transactions"); 222 | let totalAmount = 0; 223 | paymentDetails.destinations.forEach(function (payeeItem) { 224 | totalAmount += payeeObjects[payeeItem.address].amount; 225 | totalAmount += payeeObjects[payeeItem.address].fee; 226 | }); 227 | global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 228 | [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, global.config.payout.fee, paymentDetails.destinations.length]).then(function (result) { 229 | paymentDetails.destinations.forEach(function (payeeItem) { 230 | payee = payeeObjects[payeeItem.address]; 231 | payee.transactionID = result.insertId; 232 | payee.trackPayment(); 233 | }); 234 | }); 235 | } else { 236 | console.error("Unknown error from the wallet."); 237 | } 238 | }); 239 | } 240 | } 241 | }); 242 | }); 243 | }); 244 | } 245 | 246 | function init() { 247 | global.support.rpcWallet("store", [], function () { 248 | }); 249 | setInterval(function () { 250 | global.support.rpcWallet("store", [], function () { 251 | }); 252 | }, 60000); 253 | console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); 254 | makePayments(); 255 | } 256 | 257 | init(); -------------------------------------------------------------------------------- /lib/payment_systems/krb.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const async = require("async"); 3 | const debug = require("debug")("payments"); 4 | 5 | let hexChars = new RegExp("[0-9a-f]+"); 6 | let extraPaymentRound = false; 7 | let paymentTimer = null; 8 | 9 | let paymentQueue = async.queue(function (paymentDetails, callback) { 10 | if (paymentTimer !== null){ 11 | clearInterval(paymentTimer); 12 | paymentTimer = null; 13 | } 14 | debug("Making payment based on: " + JSON.stringify(paymentDetails)); 15 | let transferFunc = 'transfer'; 16 | global.support.rpcWallet(transferFunc, paymentDetails, function (body) { 17 | debug("Payment made: " + JSON.stringify(body)); 18 | if (body.hasOwnProperty('error')) { 19 | if (body.error.message === "not enough money"){ 20 | console.error("Issue making payments, not enough money, will try later"); 21 | if(!extraPaymentRound){ 22 | setTimeout(function(){ 23 | makePayments(); 24 | }, global.config.payout.timerRetry * 60 * 1000); 25 | } 26 | extraPaymentRound = true; 27 | return callback(false); 28 | } else { 29 | console.error("Issue making payments" + JSON.stringify(body.error)); 30 | console.error("Will not make more payments until the payment daemon is restarted!"); 31 | //toAddress, subject, body 32 | global.support.sendEmail(global.config.general.adminEmail, "Payment daemon unable to make payment", 33 | "Hello,\r\nThe payment daemon has hit an issue making a payment: " + JSON.stringify(body.error) + 34 | ". Please investigate and restart the payment daemon as appropriate"); 35 | return; 36 | } 37 | } 38 | if (paymentDetails.hasOwnProperty('payment_id')) { 39 | console.log("Payment made to " + paymentDetails.destinations[0].address + " with PaymentID: " + paymentDetails.payment_id + " For: " + global.support.coinToDecimal(paymentDetails.destinations[0].amount) + " XMR with a " + global.support.coinToDecimal(body.result.fee) + " XMR Mining Fee"); 40 | return callback(body.result); 41 | } else { 42 | if (transferFunc === 'transfer') { 43 | console.log("Payment made out to multiple people, total fee: " + global.support.coinToDecimal(body.result.fee) + " XMR"); 44 | } 45 | let intCount = 0; 46 | paymentDetails.destinations.forEach(function (details) { 47 | console.log("Payment made to: " + details.address + " For: " + global.support.coinToDecimal(details.amount) + " XMR"); 48 | intCount += 1; 49 | if (intCount === paymentDetails.destinations.length) { 50 | return callback(body.result); 51 | } 52 | }); 53 | } 54 | }); 55 | }, 1); 56 | 57 | paymentQueue.drain = function(){ 58 | extraPaymentRound = false; 59 | if (global.config.payout.timer > 35791){ 60 | console.error("Payout timer is too high. Please use a value under 35791 to avoid overflows."); 61 | } else { 62 | paymentTimer = setInterval(makePayments, global.config.payout.timer * 60 * 1000); 63 | } 64 | global.database.setCache('lastPaymentCycle', Math.floor(Date.now()/1000)); 65 | }; 66 | 67 | function Payee(amount, address, paymentID, bitcoin) { 68 | this.amount = amount; 69 | this.address = address; 70 | this.paymentID = paymentID; 71 | this.bitcoin = bitcoin; 72 | this.blockID = 0; 73 | this.poolType = ''; 74 | this.transactionID = 0; 75 | this.sqlID = 0; 76 | if (paymentID === null) { 77 | this.id = address; 78 | } else { 79 | this.id = address + "." + paymentID; 80 | } 81 | this.fee = 0; 82 | this.baseFee = global.support.decimalToCoin(global.config.payout.feeSlewAmount); 83 | this.setFeeAmount = function () { 84 | if (this.amount <= global.support.decimalToCoin(global.config.payout.walletMin)) { 85 | this.fee = this.baseFee; 86 | } else if (this.amount <= global.support.decimalToCoin(global.config.payout.feeSlewEnd)) { 87 | let feeValue = this.baseFee / (global.support.decimalToCoin(global.config.payout.feeSlewEnd) - global.support.decimalToCoin(global.config.payout.walletMin)); 88 | this.fee = this.baseFee - ((this.amount - global.support.decimalToCoin(global.config.payout.walletMin)) * feeValue); 89 | } 90 | this.fee = Math.floor(this.fee); 91 | }; 92 | 93 | this.makePaymentWithID = function () { 94 | let paymentDetails = { 95 | destinations: [ 96 | { 97 | amount: this.amount - this.fee, 98 | address: this.address 99 | } 100 | ], 101 | priority: global.config.payout.priority, 102 | mixin: global.config.payout.mixIn, 103 | payment_id: this.paymentID 104 | }; 105 | let identifier = this.id; 106 | let amount = this.amount; 107 | let address = this.address; 108 | let paymentID = this.paymentID; 109 | let payee = this; 110 | debug("Payment Details: " + JSON.stringify(paymentDetails)); 111 | paymentQueue.push(paymentDetails, function (body) { 112 | if (body.fee && body.fee > 10) { 113 | debug("Successful payment sent to: " + identifier); 114 | global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 115 | [0, address, paymentID, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { 116 | payee.transactionID = result.insertId; 117 | payee.trackPayment(); 118 | }); 119 | } else { 120 | console.error("Unknown error from the wallet."); 121 | } 122 | }); 123 | }; 124 | 125 | this.makePaymentAsIntegrated = function () { 126 | let paymentDetails = { 127 | destinations: [ 128 | { 129 | amount: this.amount - this.fee, 130 | address: this.address 131 | } 132 | ], 133 | priority: global.config.payout.priority, 134 | mixin: global.config.payout.mixIn 135 | }; 136 | let identifier = this.id; 137 | let amount = this.amount; 138 | let address = this.address; 139 | let payee = this; 140 | 141 | debug("Payment Details: " + JSON.stringify(paymentDetails)); 142 | paymentQueue.push(paymentDetails, function (body) { 143 | if (body.fee && body.fee > 10) { 144 | debug("Successful payment sent to: " + identifier); 145 | global.mysql.query("INSERT INTO transactions (bitcoin, address, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?)", 146 | [0, address, amount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, 1]).then(function (result) { 147 | payee.transactionID = result.insertId; 148 | payee.trackPayment(); 149 | }); 150 | } else { 151 | console.error("Unknown error from the wallet."); 152 | } 153 | }); 154 | }; 155 | 156 | this.trackPayment = function () { 157 | global.mysql.query("UPDATE balance SET amount = amount - ? WHERE id = ?", [this.amount, this.sqlID]); 158 | global.mysql.query("INSERT INTO payments (unlocked_time, paid_time, pool_type, payment_address, transaction_id, bitcoin, amount, payment_id, transfer_fee)" + 159 | " VALUES (now(), now(), ?, ?, ?, ?, ?, ?, ?)", [this.poolType, this.address, this.transactionID, this.bitcoin, this.amount - this.fee, this.paymentID, this.fee]); 160 | }; 161 | } 162 | 163 | function makePayments() { 164 | global.mysql.query("SELECT * FROM balance WHERE amount >= ?", [global.support.decimalToCoin(global.config.payout.walletMin)]).then(function (rows) { 165 | console.log("Loaded all payees into the system for processing"); 166 | let paymentDestinations = []; 167 | let totalAmount = 0; 168 | let roundCount = 0; 169 | let payeeList = []; 170 | let payeeObjects = {}; 171 | rows.forEach(function (row) { 172 | debug("Starting round for: " + JSON.stringify(row)); 173 | let payee = new Payee(row.amount, row.payment_address, row.payment_id, row.bitcoin); 174 | payeeObjects[row.payment_address] = payee; 175 | global.mysql.query("SELECT payout_threshold FROM users WHERE username = ?", [payee.id]).then(function (userRow) { 176 | roundCount += 1; 177 | let threshold = 0; 178 | if (userRow.length !== 0) { 179 | threshold = userRow[0].payout_threshold; 180 | } 181 | payee.poolType = row.pool_type; 182 | payee.sqlID = row.id; 183 | if (payee.poolType === "fees" && payee.address === global.config.payout.feeAddress && payee.amount >= ((global.support.decimalToCoin(global.config.payout.feesForTXN) + global.support.decimalToCoin(global.config.payout.exchangeMin)))) { 184 | debug("This is the fee address internal check for value"); 185 | payee.amount -= global.support.decimalToCoin(global.config.payout.feesForTXN); 186 | } else if (payee.address === global.config.payout.feeAddress && payee.poolType === "fees") { 187 | debug("Unable to pay fee address."); 188 | payee.amount = 0; 189 | } 190 | let remainder = payee.amount % (global.config.payout.denom * global.config.general.sigDivisor); 191 | if (remainder !== 0) { 192 | payee.amount -= remainder; 193 | } 194 | if (payee.amount > threshold) { 195 | payee.setFeeAmount(); 196 | if (payee.bitcoin === 0 && payee.paymentID === null && payee.amount !== 0 && payee.amount > 0 && payee.address.length !== 106) { 197 | debug("Adding " + payee.id + " to the list of people to pay (OG Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); 198 | paymentDestinations.push({amount: payee.amount - payee.fee, address: payee.address}); 199 | totalAmount += payee.amount; 200 | payeeList.push(payee); 201 | } else if ((payee.amount >= global.support.decimalToCoin(global.config.payout.exchangeMin) || (payee.amount > threshold && threshold !== 0)) && payee.bitcoin === 0) { 202 | debug("Adding " + payee.id + " to the list of people to pay (Payment ID Address). Payee balance: " + global.support.coinToDecimal(payee.amount)); 203 | payee.makePaymentWithID(); 204 | } 205 | } 206 | debug("Went: " + roundCount + " With: " + paymentDestinations.length + " Possible destinations and: " + rows.length + " Rows"); 207 | if (roundCount === rows.length && paymentDestinations.length > 0) { 208 | while (paymentDestinations.length > 0) { 209 | let paymentDetails = { 210 | destinations: paymentDestinations.splice(0, global.config.payout.maxPaymentTxns), 211 | priority: global.config.payout.priority, 212 | mixin: global.config.payout.mixIn 213 | }; 214 | console.log("Paying out: " + paymentDetails.destinations.length + " people"); 215 | paymentQueue.push(paymentDetails, function (body) { //jshint ignore:line 216 | // This is the only section that could potentially contain multiple txns. Lets do this safely eh? 217 | if (body.fee && body.fee > 10) { 218 | debug("Made it to the SQL insert for transactions"); 219 | let totalAmount = 0; 220 | paymentDetails.destinations.forEach(function (payeeItem) { 221 | totalAmount += payeeObjects[payeeItem.address].amount; 222 | totalAmount += payeeObjects[payeeItem.address].fee; 223 | }); 224 | global.mysql.query("INSERT INTO transactions (bitcoin, address, payment_id, xmr_amt, transaction_hash, mixin, fees, payees) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", 225 | [0, null, null, totalAmount, body.tx_hash.match(hexChars)[0], global.config.payout.mixIn, body.fee, paymentDetails.destinations.length]).then(function (result) { 226 | paymentDetails.destinations.forEach(function (payeeItem) { 227 | payee = payeeObjects[payeeItem.address]; 228 | payee.transactionID = result.insertId; 229 | payee.trackPayment(); 230 | }); 231 | }); 232 | } else { 233 | console.error("Unknown error from the wallet."); 234 | } 235 | }); 236 | } 237 | } 238 | }); 239 | }); 240 | }); 241 | } 242 | 243 | function init() { 244 | global.support.rpcWallet("store", [], function () { 245 | }); 246 | setInterval(function () { 247 | global.support.rpcWallet("store", [], function () { 248 | }); 249 | }, 60000); 250 | console.log("Setting the payment timer to: " + global.config.payout.timer + " minutes with a: " + global.config.payout.timerRetry + " minute delay if the wallet is out of money"); 251 | makePayments(); 252 | } 253 | 254 | init(); -------------------------------------------------------------------------------- /lib/payments.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require(global.config.coin.paymentFile); -------------------------------------------------------------------------------- /lib/remoteShare.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express = require('express'); // call express 3 | const app = express(); // define our app using express 4 | const cluster = require('cluster'); 5 | const debug = require("debug")("remoteShare"); 6 | let concat = require('concat-stream'); 7 | 8 | let threadName = ""; 9 | let workerList = []; 10 | if (cluster.isMaster) { 11 | threadName = "(Master) "; 12 | } else { 13 | threadName = "(Worker " + cluster.worker.id + " - " + process.pid + ") "; 14 | } 15 | let shareData = []; 16 | 17 | // Websocket Stuffs. 18 | app.use(function(req, res, next){ 19 | req.pipe(concat(function(data){ 20 | req.body = data; 21 | next(); 22 | })); 23 | }); 24 | 25 | // Master/Slave communication Handling 26 | function messageHandler(message) { 27 | if (typeof message.shares === "number"){ 28 | shareData.push(message); 29 | } 30 | } 31 | 32 | process.on('message', messageHandler); 33 | 34 | app.post('/leafApi', function (req, res) { 35 | try { 36 | let msgData = global.protos.WSData.decode(req.body); 37 | if (msgData.key !== global.config.api.authKey) { 38 | return res.status(403).end(); 39 | } 40 | switch (msgData.msgType) { 41 | case global.protos.MESSAGETYPE.SHARE: 42 | try { 43 | process.send(global.protos.Share.decode(msgData.msg)); 44 | } catch (e){ 45 | } 46 | return res.json({'success': true}); 47 | case global.protos.MESSAGETYPE.BLOCK: 48 | global.database.storeBlock(msgData.exInt, msgData.msg, function(data){ 49 | if (!data){ 50 | return res.status(400).end(); 51 | } else { 52 | return res.json({'success': true}); 53 | } 54 | }); 55 | break; 56 | case global.protos.MESSAGETYPE.INVALIDSHARE: 57 | global.database.storeInvalidShare(msgData.msg, function(data){ 58 | if (!data){ 59 | return res.status(400).end(); 60 | } else { 61 | return res.json({'success': true}); 62 | } 63 | }); 64 | break; 65 | default: 66 | return res.status(400).end(); 67 | } 68 | } catch (e) { 69 | console.log("Invalid WS frame"); 70 | return res.status(400).end(); 71 | } 72 | }); 73 | 74 | function storeShares(){ 75 | if (Object.keys(shareData).length > 0){ 76 | global.database.storeBulkShares(shareData); 77 | shareData = []; 78 | } 79 | setTimeout(storeShares, 1000); 80 | } 81 | 82 | if (cluster.isMaster) { 83 | let numWorkers = require('os').cpus().length; 84 | console.log('Master cluster setting up ' + numWorkers + ' workers...'); 85 | storeShares(); 86 | 87 | for (let i = 0; i < numWorkers; i++) { 88 | let worker = cluster.fork(); 89 | worker.on('message', messageHandler); 90 | workerList.push(worker); 91 | } 92 | 93 | cluster.on('online', function (worker) { 94 | console.log('Worker ' + worker.process.pid + ' is online'); 95 | }); 96 | 97 | cluster.on('exit', function (worker, code, signal) { 98 | console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal); 99 | console.log('Starting a new worker'); 100 | worker = cluster.fork(); 101 | worker.on('message', messageHandler); 102 | workerList.push(worker); 103 | }); 104 | } else { 105 | app.listen(8000, function () { 106 | console.log('Process ' + process.pid + ' is listening to all incoming requests'); 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /lib/remote_comms.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const request = require('request'); 3 | const async = require('async'); 4 | 5 | function Database() { 6 | 7 | let thread_id=''; 8 | 9 | this.sendQueue = async.queue(function (task, callback) { 10 | async.doUntil( 11 | function (intCallback) { 12 | request.post({url: global.config.general.shareHost, body: task.body, forever: true}, function (error, response, body) { 13 | if (!error) { 14 | return intCallback(null, response.statusCode); 15 | } 16 | return intCallback(null, 0); 17 | }); 18 | }, 19 | function (data) { 20 | return data === 200; 21 | }, 22 | function () { 23 | callback(); 24 | }); 25 | }, require('os').cpus().length*32); 26 | 27 | this.storeShare = function (blockId, shareData) { 28 | let wsData = global.protos.WSData.encode({ 29 | msgType: global.protos.MESSAGETYPE.SHARE, 30 | key: global.config.api.authKey, 31 | msg: shareData, 32 | exInt: blockId 33 | }); 34 | process.send({type: 'sendRemote', body: wsData.toString('hex')}); 35 | }; 36 | 37 | this.storeBlock = function (blockId, blockData) { 38 | let wsData = global.protos.WSData.encode({ 39 | msgType: global.protos.MESSAGETYPE.BLOCK, 40 | key: global.config.api.authKey, 41 | msg: blockData, 42 | exInt: blockId 43 | }); 44 | process.send({type: 'sendRemote', body: wsData.toString('hex')}); 45 | }; 46 | 47 | this.storeInvalidShare = function (minerData) { 48 | let wsData = global.protos.WSData.encode({ 49 | msgType: global.protos.MESSAGETYPE.INVALIDSHARE, 50 | key: global.config.api.authKey, 51 | msg: minerData, 52 | exInt: 1 53 | }); 54 | process.send({type: 'sendRemote', body: wsData.toString('hex')}); 55 | }; 56 | 57 | setInterval(function(queue_obj){ 58 | if (global.database.thread_id === '(Master) '){ 59 | console.log(global.database.thread_id + "Queue debug state: " + queue_obj.length() + " items in the queue " + queue_obj.running() + " items being processed"); 60 | } 61 | }, 5000, this.sendQueue); 62 | 63 | 64 | this.initEnv = function(){ 65 | this.data = null; 66 | }; 67 | } 68 | 69 | module.exports = Database; -------------------------------------------------------------------------------- /lib/support.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const CircularBuffer = require('circular-buffer'); 3 | const request = require('request'); 4 | const requestJson = require('request-json'); 5 | const moment = require('moment'); 6 | const debug = require('debug')('support'); 7 | const fs = require('fs'); 8 | 9 | function circularBuffer(size) { 10 | let buffer = CircularBuffer(size); 11 | 12 | buffer.sum = function () { 13 | if (this.size() === 0) { 14 | return 1; 15 | } 16 | return this.toarray().reduce(function (a, b) { 17 | return a + b; 18 | }); 19 | }; 20 | 21 | buffer.average = function (lastShareTime) { 22 | if (this.size() === 0) { 23 | return global.config.pool.targetTime * 1.5; 24 | } 25 | let extra_entry = (Date.now() / 1000) - lastShareTime; 26 | return (this.sum() + Math.round(extra_entry)) / (this.size() + 1); 27 | }; 28 | 29 | buffer.clear = function () { 30 | let i = this.size(); 31 | while (i > 0) { 32 | this.deq(); 33 | i = this.size(); 34 | } 35 | }; 36 | 37 | return buffer; 38 | } 39 | 40 | function sendEmail(toAddress, subject, body){ 41 | request.post(global.config.general.mailgunURL + "/messages", { 42 | auth: { 43 | user: 'api', 44 | pass: global.config.general.mailgunKey 45 | }, 46 | form: { 47 | from: global.config.general.emailFrom, 48 | to: toAddress, 49 | subject: subject, 50 | text: body 51 | } 52 | }, function(err, response, body){ 53 | if (!err && response.statusCode === 200) { 54 | console.log("Email sent successfully! Response: " + body); 55 | } else { 56 | console.error("Did not send e-mail successfully! Response: " + body + " Response: "+JSON.stringify(response)); 57 | } 58 | }); 59 | } 60 | 61 | function jsonRequest(host, port, data, callback, path) { 62 | path = path || 'json_rpc'; 63 | let uri; 64 | if (global.config.rpc.https) { 65 | uri = "https://" + host + ":" + port + "/"; 66 | } else { 67 | uri = "http://" + host + ":" + port + "/"; 68 | } 69 | debug("JSON URI: " + uri + path + " Args: " + JSON.stringify(data)); 70 | let client = requestJson.createClient(uri, {timeout: 300000}); 71 | client.headers["Content-Type"] = "application/json"; 72 | client.headers["Content-Length"] = data.length; 73 | client.headers["Accept"] = "application/json"; 74 | if (global.config.payout.rpcPasswordEnabled && host === global.config.wallet.address && port === global.config.wallet.port){ 75 | fs.readFile(global.config.payout.rpcPasswordPath, 'utf8', function(err, data){ 76 | if (err){ 77 | console.error("RPC password enabled, unable to read the file due to: " + JSON.stringify(err)); 78 | return; 79 | } 80 | let passData = data.split(":"); 81 | client.setBasicAuth(passData[0], passData[1]); 82 | request.post(uri, { 83 | auth:{ 84 | user: passData[0], 85 | pass: passData[1], 86 | sendImmediately: false 87 | }, 88 | data: JSON.stringify(data) 89 | }, function (err, res, body) { 90 | if (err) { 91 | return callback(err); 92 | } 93 | debug("JSON result: " + JSON.stringify(body)); 94 | return callback(body); 95 | }); 96 | }); 97 | } else { 98 | client.post(path, data, function (err, res, body) { 99 | if (err) { 100 | return callback(err); 101 | } 102 | debug("JSON result: " + JSON.stringify(body)); 103 | return callback(body); 104 | }); 105 | } 106 | } 107 | 108 | function rpc(host, port, method, params, callback) { 109 | 110 | let data = { 111 | id: "0", 112 | jsonrpc: "2.0", 113 | method: method, 114 | params: params 115 | }; 116 | return jsonRequest(host, port, data, callback); 117 | } 118 | function formatDate(date) { 119 | // Date formatting for MySQL date time fields. 120 | return moment(date).format('YYYY-MM-DD HH:mm:ss'); 121 | } 122 | 123 | function formatDateFromSQL(date) { 124 | // Date formatting for MySQL date time fields. 125 | let ts = new Date(date); 126 | return Math.floor(ts.getTime() / 1000); 127 | } 128 | 129 | function coinToDecimal(amount) { 130 | return amount / global.config.coin.sigDigits; 131 | } 132 | 133 | function decimalToCoin(amount) { 134 | return Math.round(amount * global.config.coin.sigDigits); 135 | } 136 | 137 | function bitcoinDecimalToCoin(amount) { 138 | return Math.round(amount * 100000000); 139 | } 140 | 141 | function bitcoinCoinToDecimal(amount) { 142 | return amount / 100000000; 143 | } 144 | 145 | function blockCompare(a, b) { 146 | if (a.height < b.height) { 147 | return 1; 148 | } 149 | 150 | if (a.height > b.height) { 151 | return -1; 152 | } 153 | return 0; 154 | } 155 | 156 | function tsCompare(a, b) { 157 | if (a.ts < b.ts) { 158 | return 1; 159 | } 160 | 161 | if (a.ts > b.ts) { 162 | return -1; 163 | } 164 | return 0; 165 | } 166 | 167 | module.exports = function () { 168 | return { 169 | rpcDaemon: function (method, params, callback) { 170 | rpc(global.config.daemon.address, global.config.daemon.port, method, params, callback); 171 | }, 172 | rpcWallet: function (method, params, callback) { 173 | rpc(global.config.wallet.address, global.config.wallet.port, method, params, callback); 174 | }, 175 | jsonRequest: jsonRequest, 176 | circularBuffer: circularBuffer, 177 | formatDate: formatDate, 178 | coinToDecimal: coinToDecimal, 179 | decimalToCoin: decimalToCoin, 180 | bitcoinDecimalToCoin: bitcoinDecimalToCoin, 181 | bitcoinCoinToDecimal: bitcoinCoinToDecimal, 182 | formatDateFromSQL: formatDateFromSQL, 183 | blockCompare: blockCompare, 184 | sendEmail: sendEmail, 185 | tsCompare: tsCompare 186 | }; 187 | }; 188 | -------------------------------------------------------------------------------- /lib/worker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const debug = require("debug")("worker"); 3 | const async = require("async"); 4 | const sprintf = require("sprintf-js").sprintf; 5 | 6 | let threadName = "Worker Server "; 7 | let cycleCount = 0; 8 | let lastBlockHash = null; 9 | 10 | function updateShareStats() { 11 | // This is an omni-worker to deal with all things share-stats related 12 | // Time based averages are worked out on ring buffers. 13 | // Buffer lengths? You guessed it, configured in SQL. 14 | // Stats timeouts are 30 seconds, so everything for buffers should be there. 15 | let currentTime = Date.now(); 16 | let activeAddresses = []; 17 | async.waterfall([ 18 | function (callback) { 19 | global.coinFuncs.getLastBlockHeader(function (err, body) { 20 | if (err !== null){ 21 | return callback(err, "Invalid block header"); 22 | } 23 | callback(null, body.height + 1); 24 | }); 25 | }, 26 | function (height, callback) { 27 | let locTime = Date.now() - 600000; 28 | let identifierTime = Date.now() - 1800000; 29 | let localStats = {pplns: 0, pps: 0, solo: 0, prop: 0, global: 0, miners: {}}; 30 | let localMinerCount = {pplns: 0, pps: 0, solo: 0, prop: 0, global: 0}; 31 | let localTimes = { 32 | pplns: locTime, pps: locTime, solo: locTime, prop: locTime, 33 | global: locTime, miners: {} 34 | }; 35 | let minerList = []; 36 | let identifiers = {}; 37 | let loopBreakout = 0; 38 | async.doUntil(function (callback_until) { 39 | let oldestTime = Date.now(); 40 | let loopCount = 0; 41 | let txn = global.database.env.beginTxn({readOnly: true}); 42 | let cursor = new global.database.lmdb.Cursor(txn, global.database.shareDB); 43 | for (let found = (cursor.goToRange(height) === height); found; found = cursor.goToNextDup()) { 44 | cursor.getCurrentBinary(function (key, share) { // jshint ignore:line 45 | try { 46 | share = global.protos.Share.decode(share); 47 | } catch (e) { 48 | console.error(share); 49 | return; 50 | } 51 | if (share.timestamp < oldestTime) { 52 | oldestTime = share.timestamp; 53 | } 54 | if (share.timestamp <= identifierTime) { 55 | return; 56 | } 57 | let minerID = share.paymentAddress; 58 | if (typeof(share.paymentID) !== 'undefined' && share.paymentID.length > 10) { 59 | minerID = minerID + '.' + share.paymentID; 60 | } 61 | if (minerID in identifiers && identifiers[minerID].indexOf(share.identifier) >= 0) { 62 | loopCount += 1; 63 | } else if (minerID in identifiers) { 64 | identifiers[minerID].push(share.identifier); 65 | } else { 66 | identifiers[minerID] = [share.identifier]; 67 | } 68 | if (share.timestamp <= locTime) { 69 | return; 70 | } 71 | let minerIDWithIdentifier = minerID + "_" + share.identifier; 72 | localStats.global += share.shares; 73 | if (localTimes.global <= share.timestamp) { 74 | localTimes.global = share.timestamp; 75 | } 76 | let minerType; 77 | switch (share.poolType) { 78 | case global.protos.POOLTYPE.PPLNS: 79 | minerType = 'pplns'; 80 | localStats.pplns += share.shares; 81 | if (localTimes.pplns <= share.timestamp) { 82 | localTimes.pplns = share.timestamp; 83 | } 84 | break; 85 | case global.protos.POOLTYPE.PPS: 86 | localStats.pps += share.shares; 87 | minerType = 'pps'; 88 | if (localTimes.pps <= share.timestamp) { 89 | localTimes.pps = share.timestamp; 90 | } 91 | break; 92 | case global.protos.POOLTYPE.SOLO: 93 | localStats.solo += share.shares; 94 | minerType = 'solo'; 95 | if (localTimes.solo <= share.timestamp) { 96 | localTimes.solo = share.timestamp; 97 | } 98 | break; 99 | } 100 | if (minerList.indexOf(minerID) >= 0) { 101 | localStats.miners[minerID] += share.shares; 102 | if (localTimes.miners[minerID] < share.timestamp) { 103 | localTimes.miners[minerID] = share.timestamp; 104 | } 105 | } else { 106 | localMinerCount[minerType] += 1; 107 | localMinerCount.global += 1; 108 | localStats.miners[minerID] = share.shares; 109 | localTimes.miners[minerID] = share.timestamp; 110 | minerList.push(minerID); 111 | } 112 | if (minerList.indexOf(minerIDWithIdentifier) >= 0) { 113 | localStats.miners[minerIDWithIdentifier] += share.shares; 114 | if (localTimes.miners[minerIDWithIdentifier] < share.timestamp) { 115 | localTimes.miners[minerIDWithIdentifier] = share.timestamp; 116 | } 117 | } else { 118 | localStats.miners[minerIDWithIdentifier] = share.shares; 119 | localTimes.miners[minerIDWithIdentifier] = share.timestamp; 120 | minerList.push(minerIDWithIdentifier); 121 | } 122 | }); 123 | } 124 | cursor.close(); 125 | txn.abort(); 126 | return callback_until(null, oldestTime); 127 | }, function (oldestTime) { 128 | height -= 1; 129 | loopBreakout += 1; 130 | if (loopBreakout > 60 || height < 0) { 131 | return true; 132 | } 133 | return oldestTime <= identifierTime; 134 | }, function (err) { 135 | // todo: Need to finish parsing the cached data into caches for caching purproses. 136 | let globalMinerList = global.database.getCache('minerList'); 137 | if (globalMinerList === false) { 138 | globalMinerList = []; 139 | } 140 | let cache_updates = {}; 141 | // pplns: 0, pps: 0, solo: 0, prop: 0, global: 0 142 | ['pplns', 'pps', 'solo', 'prop', 'global'].forEach(function (key) { 143 | let cachedData = global.database.getCache(key + "_stats"); 144 | if (cachedData !== false) { 145 | cachedData.hash = Math.floor(localStats[key] / 600); 146 | cachedData.lastHash = localTimes[key]; 147 | cachedData.minerCount = localMinerCount[key]; 148 | if (!cachedData.hasOwnProperty("hashHistory")) { 149 | cachedData.hashHistory = []; 150 | cachedData.minerHistory = []; 151 | } 152 | if (cycleCount === 0) { 153 | cachedData.hashHistory.unshift({ts: currentTime, hs: cachedData.hash}); 154 | if (cachedData.hashHistory.length > global.config.general.statsBufferLength) { 155 | while (cachedData.hashHistory.length > global.config.general.statsBufferLength) { 156 | cachedData.hashHistory.pop(); 157 | } 158 | } 159 | cachedData.minerHistory.unshift({ts: currentTime, cn: cachedData.minerCount}); 160 | if (cachedData.minerHistory.length > global.config.general.statsBufferLength) { 161 | while (cachedData.minerHistory.length > global.config.general.statsBufferLength) { 162 | cachedData.minerHistory.pop(); 163 | } 164 | } 165 | } 166 | } else { 167 | cachedData = { 168 | hash: Math.floor(localStats[key] / 600), 169 | totalHashes: 0, 170 | lastHash: localTimes[key], 171 | minerCount: localMinerCount[key], 172 | hashHistory: [{ts: currentTime, hs: cachedData.hash}], 173 | minerHistory: [{ts: currentTime, cn: cachedData.hash}] 174 | }; 175 | } 176 | cache_updates[key + "_stats"] = cachedData; 177 | }); 178 | minerList.forEach(function (miner) { 179 | if (globalMinerList.indexOf(miner) === -1) { 180 | globalMinerList.push(miner); 181 | } 182 | if (miner.indexOf('_') === -1){ 183 | activeAddresses.push(miner); 184 | } 185 | let cachedData = global.database.getCache(miner); 186 | if (cachedData !== false) { 187 | cachedData.hash = Math.floor(localStats.miners[miner] / 600); 188 | cachedData.lastHash = localTimes.miners[miner]; 189 | if (!cachedData.hasOwnProperty("hashHistory")) { 190 | cachedData.hashHistory = []; 191 | } 192 | if (cycleCount === 0){ 193 | cachedData.hashHistory.unshift({ts: currentTime, hs: cachedData.hash}); 194 | if (cachedData.hashHistory.length > global.config.general.statsBufferLength) { 195 | while (cachedData.hashHistory.length > global.config.general.statsBufferLength) { 196 | cachedData.hashHistory.pop(); 197 | } 198 | } 199 | } 200 | } else { 201 | cachedData = { 202 | hash: Math.floor(localStats.miners[miner] / 600), 203 | totalHashes: 0, 204 | lastHash: localTimes.miners[miner], 205 | hashHistory: [{ts: currentTime, hs: cachedData.hash}], 206 | goodShares: 0, 207 | badShares: 0 208 | }; 209 | } 210 | cache_updates[miner] = cachedData; 211 | }); 212 | globalMinerList.forEach(function (miner) { 213 | if (minerList.indexOf(miner) === -1) { 214 | let minerStats = global.database.getCache(miner); 215 | if (minerStats.hash !== 0) { 216 | console.log("Removing: " + miner + " as an active miner from the cache."); 217 | if (miner.indexOf('_') > -1) { 218 | // This is a worker case. 219 | let address_parts = miner.split('_'); 220 | let address = address_parts[0]; 221 | let worker = address_parts[1]; 222 | global.mysql.query("SELECT email FROM users WHERE username = ? AND enable_email IS true limit 1", [address]).then(function (rows) { 223 | if (rows.length === 0) { 224 | return; 225 | } 226 | // toAddress, subject, body 227 | let emailData = { 228 | worker: worker, 229 | timestamp: global.support.formatDate(Date.now()), 230 | poolEmailSig: global.config.general.emailSig 231 | }; 232 | global.support.sendEmail(rows[0].email, 233 | sprintf(global.config.email.workerNotHashingSubject, emailData), 234 | sprintf(global.config.email.workerNotHashingBody, emailData)); 235 | }); 236 | } 237 | minerStats.hash = 0; 238 | cache_updates[miner] = minerStats; 239 | } 240 | } 241 | }); 242 | Object.keys(identifiers).forEach(function (key) { 243 | cache_updates[key + '_identifiers'] = identifiers[key]; 244 | }); 245 | cache_updates.minerList = globalMinerList; 246 | global.database.bulkSetCache(cache_updates); 247 | callback(null); 248 | }); 249 | } 250 | ], function (err, result) { 251 | cycleCount += 1; 252 | if (cycleCount === 6){ 253 | cycleCount = 0; 254 | } 255 | }); 256 | setTimeout(updateShareStats, 10000); 257 | } 258 | 259 | function updatePoolStats(poolType) { 260 | let cache; 261 | if (typeof(poolType) !== 'undefined') { 262 | cache = global.database.getCache(poolType + "_stats"); 263 | } else { 264 | cache = global.database.getCache("global_stats"); 265 | } 266 | async.series([ 267 | function (callback) { 268 | debug(threadName + "Checking Influx for last 10min avg for pool stats"); 269 | return callback(null, cache.hash || 0); 270 | }, 271 | function (callback) { 272 | debug(threadName + "Checking Influx for last 10min avg for miner count for pool stats"); 273 | return callback(null, cache.minerCount || 0); 274 | }, 275 | function (callback) { 276 | debug(threadName + "Checking Influx for last 10min avg for miner count for pool stats"); 277 | return callback(null, cache.totalHashes || 0); 278 | }, 279 | function (callback) { 280 | debug(threadName + "Checking MySQL for last block find time for pool stats"); 281 | let cacheData = global.database.getBlockList(poolType); 282 | if (cacheData.length === 0) { 283 | return callback(null, 0); 284 | } 285 | return callback(null, Math.floor(cacheData[0].ts / 1000)); 286 | }, 287 | function (callback) { 288 | debug(threadName + "Checking MySQL for last block find time for pool stats"); 289 | let cacheData = global.database.getBlockList(poolType); 290 | if (cacheData.length === 0) { 291 | return callback(null, 0); 292 | } 293 | return callback(null, cacheData[0].height); 294 | }, 295 | function (callback) { 296 | debug(threadName + "Checking MySQL for block count for pool stats"); 297 | return callback(null, global.database.getBlockList(poolType).length); 298 | }, 299 | function (callback) { 300 | debug(threadName + "Checking MySQL for total miners paid"); 301 | if (typeof(poolType) !== 'undefined') { 302 | global.mysql.query("SELECT payment_address, payment_id FROM payments WHERE pool_type = ? group by payment_address, payment_id", [poolType]).then(function (rows) { 303 | return callback(null, rows.length); 304 | }); 305 | } else { 306 | global.mysql.query("SELECT payment_address, payment_id FROM payments group by payment_address, payment_id").then(function (rows) { 307 | return callback(null, rows.length); 308 | }); 309 | } 310 | }, 311 | function (callback) { 312 | debug(threadName + "Checking MySQL for total transactions count"); 313 | if (typeof(poolType) !== 'undefined') { 314 | global.mysql.query("SELECT distinct(transaction_id) from payments WHERE pool_type = ?", [poolType]).then(function (rows) { 315 | return callback(null, rows.length); 316 | }); 317 | } else { 318 | global.mysql.query("SELECT count(id) as txn_count FROM transactions").then(function (rows) { 319 | if (typeof(rows[0]) !== 'undefined') { 320 | return callback(null, rows[0].txn_count); 321 | } else { 322 | return callback(null, 0); 323 | } 324 | }); 325 | } 326 | }, 327 | function (callback) { 328 | debug(threadName + "Checking Influx for last 10min avg for miner count for pool stats"); 329 | return callback(null, cache.roundHashes || 0); 330 | } 331 | ], function (err, result) { 332 | if (typeof(poolType) === 'undefined') { 333 | poolType = 'global'; 334 | } 335 | global.database.setCache('pool_stats_' + poolType, { 336 | hashRate: result[0], 337 | miners: result[1], 338 | totalHashes: result[2], 339 | lastBlockFoundTime: result[3] || 0, 340 | lastBlockFound: result[4] || 0, 341 | totalBlocksFound: result[5] || 0, 342 | totalMinersPaid: result[6] || 0, 343 | totalPayments: result[7] || 0, 344 | roundHashes: result[8] || 0 345 | }); 346 | }); 347 | } 348 | 349 | function updatePoolPorts(poolServers) { 350 | debug(threadName + "Updating pool ports"); 351 | let local_cache = {global: []}; 352 | let portCount = 0; 353 | global.mysql.query("select * from ports where hidden = 0 and lastSeen >= NOW() - INTERVAL 10 MINUTE").then(function (rows) { 354 | rows.forEach(function (row) { 355 | portCount += 1; 356 | if (!local_cache.hasOwnProperty(row.port_type)) { 357 | local_cache[row.port_type] = []; 358 | } 359 | local_cache[row.port_type].push({ 360 | host: poolServers[row.pool_id], 361 | port: row.network_port, 362 | difficulty: row.starting_diff, 363 | description: row.description, 364 | miners: row.miners 365 | }); 366 | if (portCount === rows.length) { 367 | let local_counts = {}; 368 | let port_diff = {}; 369 | let port_miners = {}; 370 | let pool_type_count = 0; 371 | let localPortInfo = {}; 372 | for (let pool_type in local_cache) { // jshint ignore:line 373 | pool_type_count += 1; 374 | local_cache[pool_type].forEach(function (portData) { // jshint ignore:line 375 | if (!local_counts.hasOwnProperty(portData.port)) { 376 | local_counts[portData.port] = 0; 377 | } 378 | if (!port_diff.hasOwnProperty(portData.port)) { 379 | port_diff[portData.port] = portData.difficulty; 380 | } 381 | if (!port_miners.hasOwnProperty(portData.port)) { 382 | port_miners[portData.port] = 0; 383 | } 384 | if (port_diff[portData.port] === portData.difficulty) { 385 | local_counts[portData.port] += 1; 386 | port_miners[portData.port] += portData.miners; 387 | } 388 | localPortInfo[portData.port] = portData.description; 389 | if (local_counts[portData.port] === Object.keys(poolServers).length) { 390 | local_cache.global.push({ 391 | host: { 392 | blockID: local_cache[pool_type][0].host.blockID, 393 | blockIDTime: local_cache[pool_type][0].host.blockIDTime, 394 | hostname: global.config.pool.geoDNS, 395 | }, 396 | port: portData.port, 397 | pool_type: pool_type, 398 | difficulty: portData.difficulty, 399 | miners: port_miners[portData.port], 400 | description: localPortInfo[portData.port] 401 | }); 402 | } 403 | }); 404 | if (pool_type_count === Object.keys(local_cache).length) { 405 | debug(threadName + "Sending the following to the workers: " + JSON.stringify(local_cache)); 406 | global.database.setCache('poolPorts', local_cache); 407 | } 408 | } 409 | } 410 | }); 411 | }); 412 | } 413 | 414 | function updatePoolInformation() { 415 | let local_cache = {}; 416 | debug(threadName + "Updating pool information"); 417 | global.mysql.query("select * from pools where last_checkin >= NOW() - INTERVAL 10 MINUTE").then(function (rows) { 418 | rows.forEach(function (row) { 419 | local_cache[row.id] = { 420 | ip: row.ip, 421 | blockID: row.blockID, 422 | blockIDTime: global.support.formatDateFromSQL(row.blockIDTime), 423 | hostname: row.hostname 424 | }; 425 | if (Object.keys(local_cache).length === rows.length) { 426 | global.database.setCache('poolServers', local_cache); 427 | updatePoolPorts(local_cache); 428 | } 429 | }); 430 | }); 431 | } 432 | 433 | function updateBlockHeader() { 434 | // Todo: Implement within the coins/.js file. 435 | global.support.rpcDaemon('getlastblockheader', [], function (body) { 436 | if (typeof body.error !== 'undefined'){ 437 | return console.error(`Issue getting last block header: ${JSON.stringify(body)}`); 438 | } 439 | if (body.result && body.result.block_header.hash !== lastBlockHash) { 440 | lastBlockHash = body.result.block_header.hash; 441 | global.database.setCache('networkBlockInfo', { 442 | difficulty: body.result.block_header.difficulty, 443 | hash: body.result.block_header.hash, 444 | height: body.result.block_header.height, 445 | value: body.result.block_header.reward, 446 | ts: body.result.block_header.timestamp 447 | }); 448 | } else if (body.result.block_header.hash === lastBlockHash) { 449 | console.log("Block headers identical to historical header. Ignoring"); 450 | } else { 451 | console.error("GetLastBlockHeader Error during block header update"); 452 | } 453 | }); 454 | } 455 | 456 | function updateWalletStats() { 457 | async.waterfall([ 458 | function (callback) { 459 | // Todo: Implement within the coins/.js file. 460 | global.support.rpcWallet('getbalance', [], function (body) { 461 | if (body.result) { 462 | return callback(null, { 463 | balance: body.result.balance, 464 | unlocked: body.result.unlocked_balance, 465 | ts: Date.now() 466 | }); 467 | } else { 468 | return callback(true, "Unable to process balance"); 469 | } 470 | }); 471 | }, 472 | function (state, callback) { 473 | // Todo: Implement within the coins/.js file. 474 | global.support.rpcWallet('getheight', [], function (body) { 475 | if (body.result) { 476 | state.height = body.result.height; 477 | return callback(null, state); 478 | } else if (typeof body.error !== 'undefined' && body.error.message === 'Method not found') { 479 | state.height = 0; 480 | return callback(null, state); 481 | } else { 482 | return callback(true, "Unable to get current wallet height"); 483 | } 484 | }); 485 | } 486 | ], function (err, results) { 487 | if (err) { 488 | return console.error("Unable to get wallet stats: " + results); 489 | } 490 | global.database.setCache('walletStateInfo', results); 491 | let history = global.database.getCache('walletHistory'); 492 | if (history === false) { 493 | history = []; 494 | } 495 | history.unshift(results); 496 | history = history.sort(global.support.tsCompare); 497 | if (history.length > global.config.general.statsBufferLength) { 498 | while (history.length > global.config.general.statsBufferLength) { 499 | history.pop(); 500 | } 501 | } 502 | global.database.setCache('walletHistory', history); 503 | }); 504 | 505 | } 506 | 507 | let lastBlockCheckIsOk = true; 508 | function monitorNodes() { 509 | global.coinFuncs.getLastBlockHeader((err, block) => { 510 | if (err !== null) { 511 | if (lastBlockCheckIsOk) { 512 | lastBlockCheckIsOk = false; 513 | global.support.sendEmail( 514 | global.config.general.adminEmail, 515 | 'Failed to query daemon for last block header', 516 | `The worker failed to return last block header. Please verify if the daemon is running properly.` 517 | ); 518 | } 519 | return 520 | } 521 | if (!lastBlockCheckIsOk) { 522 | lastBlockCheckIsOk = true; 523 | global.support.sendEmail( 524 | global.config.general.adminEmail, 525 | 'Quering daemon for last block header is back to normal', 526 | `An warning was sent to you indicating that the the worker failed to return the last block header. 527 | The issue seems to be solved now.` 528 | ); 529 | } 530 | const sql = 'SELECT blockID, hostname, ip FROM pools WHERE last_checkin > DATE_SUB(NOW(), INTERVAL 30 MINUTE)'; 531 | global.mysql.query(sql).then(pools => { 532 | pools.forEach(({ blockID, hostname, ip }) => { 533 | if (blockID < block.height - 3) { 534 | global.support.sendEmail( 535 | global.config.general.adminEmail, 536 | 'Pool server is behind in blocks', 537 | `The pool server: ${hostname} with IP: ${ip} is ${(block.height - blockID)} blocks behind.` 538 | ); 539 | } 540 | }) 541 | }); 542 | 543 | }); 544 | } 545 | 546 | updateShareStats(); 547 | updateBlockHeader(); 548 | updatePoolStats(); 549 | updatePoolInformation(); 550 | updateWalletStats(); 551 | monitorNodes(); 552 | setInterval(updateBlockHeader, 10000); 553 | setInterval(updatePoolStats, 5000); 554 | setInterval(updatePoolStats, 5000, 'pplns'); 555 | setInterval(updatePoolStats, 5000, 'pps'); 556 | setInterval(updatePoolStats, 5000, 'solo'); 557 | setInterval(updatePoolInformation, 5000); 558 | setInterval(updateWalletStats, 60000); 559 | setInterval(monitorNodes, 300000); 560 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-pool", 3 | "version": "0.0.1", 4 | "description": "Fairly simple universal cryptonote pool", 5 | "main": "init.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/Snipa22/node-crypto-pool.git" 9 | }, 10 | "author": "Alexander Blair", 11 | "license": "MIT", 12 | "dependencies": { 13 | "async": "2.1.4", 14 | "bignum": "^0.12.5", 15 | "bluebird": "3.4.7", 16 | "body-parser": "^1.16.0", 17 | "bufferutil": "^1.3.0", 18 | "circular-buffer": "1.0.2", 19 | "cluster": "0.7.7", 20 | "concat-stream": "^1.6.0", 21 | "cors": "^2.8.1", 22 | "crypto": "0.0.3", 23 | "debug": "2.5.1", 24 | "express": "4.14.0", 25 | "jsonwebtoken": "^7.2.1", 26 | "minimist": "1.2.0", 27 | "moment": "2.17.1", 28 | "mysql": "2.15.0", 29 | "node-lmdb": "0.4.12", 30 | "promise-mysql": "3.0.0", 31 | "protocol-buffers": "^3.2.1", 32 | "range": "0.0.3", 33 | "redis": "^2.6.5", 34 | "request": "^2.79.0", 35 | "request-json": "0.6.1", 36 | "shapeshift.io": "1.3.0", 37 | "socketio": "^1.0.0", 38 | "sprintf-js": "^1.0.3", 39 | "sticky-cluster": "^0.3.1", 40 | "uuid": "3.0.1", 41 | "wallet-address-validator": "0.1.0", 42 | "zmq": "^2.15.3" 43 | }, 44 | "optionalDependencies": { 45 | "cryptonote-util": "git://github.com/Snipa22/node-cryptonote-util.git#xmr-Nan-2.0", 46 | "cryptonight-hashing": "git+https://github.com/MoneroOcean/node-cryptonight-hashing.git#865a1f18e7dd5cf0513ae6becfdbeba3a10c9fb9", 47 | "multi-hashing": "git+https://github.com/Snipa22/node-multi-hashing-aesni.git#v0.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sample_config.sql: -------------------------------------------------------------------------------- 1 | UPDATE pool.config SET item_value = '' WHERE module = 'pool' and item = 'address'; 2 | UPDATE pool.config SET item_value = '' WHERE module = 'payout' and item = 'feeAddress'; 3 | UPDATE pool.config SET item_value = '' WHERE module = 'general' and item = 'mailgunKey'; 4 | UPDATE pool.config SET item_value = '' WHERE module = 'general' and item = 'mailgunURL'; 5 | UPDATE pool.config SET item_value = '' WHERE module = 'general' and item = 'emailFrom'; 6 | UPDATE pool.config SET item_value = '' WHERE module = 'general' and item = 'shareHost'; 7 | -------------------------------------------------------------------------------- /sql_sync/config_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "module": "pool", 5 | "item": "address", 6 | "item_value": "", 7 | "item_type": "string", 8 | "Item_desc": "Address to mine to, this should be the wallet-rpc address." 9 | }, 10 | { 11 | "id": 2, 12 | "module": "pool", 13 | "item": "minerTimeout", 14 | "item_value": "900", 15 | "item_type": "int", 16 | "Item_desc": "Length of time before a miner is flagged inactive." 17 | }, 18 | { 19 | "id": 3, 20 | "module": "pool", 21 | "item": "banEnabled", 22 | "item_value": "true", 23 | "item_type": "bool", 24 | "Item_desc": "Enables/disabled banning of \"bad\" miners." 25 | }, 26 | { 27 | "id": 4, 28 | "module": "pool", 29 | "item": "banLength", 30 | "item_value": "-15m", 31 | "item_type": "string", 32 | "Item_desc": "Ban duration except perma-bans" 33 | }, 34 | { 35 | "id": 5, 36 | "module": "pool", 37 | "item": "targetTime", 38 | "item_value": "30", 39 | "item_type": "int", 40 | "Item_desc": "Time in seconds between share finds" 41 | }, 42 | { 43 | "id": 6, 44 | "module": "pool", 45 | "item": "trustThreshold", 46 | "item_value": "30", 47 | "item_type": "int", 48 | "Item_desc": "Number of shares before miner trust can kick in." 49 | }, 50 | { 51 | "id": 8, 52 | "module": "pool", 53 | "item": "banPercent", 54 | "item_value": "25", 55 | "item_type": "int", 56 | "Item_desc": "Percentage of shares that need to be invalid to be banned." 57 | }, 58 | { 59 | "id": 9, 60 | "module": "pool", 61 | "item": "banThreshold", 62 | "item_value": "30", 63 | "item_type": "int", 64 | "Item_desc": "Number of shares before bans can begin" 65 | }, 66 | { 67 | "id": 10, 68 | "module": "pool", 69 | "item": "trustedMiners", 70 | "item_value": "true", 71 | "item_type": "bool", 72 | "Item_desc": "Enable the miner trust system" 73 | }, 74 | { 75 | "id": 11, 76 | "module": "pool", 77 | "item": "trustChange", 78 | "item_value": "1", 79 | "item_type": "int", 80 | "Item_desc": "Change in the miner trust in percent" 81 | }, 82 | { 83 | "id": 12, 84 | "module": "pool", 85 | "item": "trustMin", 86 | "item_value": "20", 87 | "item_type": "int", 88 | "Item_desc": "Minimum level of miner trust" 89 | }, 90 | { 91 | "id": 13, 92 | "module": "pool", 93 | "item": "trustPenalty", 94 | "item_value": "30", 95 | "item_type": "int", 96 | "Item_desc": "Number of shares that must be successful to be trusted, reset to this value if trust share is broken" 97 | }, 98 | { 99 | "id": 14, 100 | "module": "pool", 101 | "item": "retargetTime", 102 | "item_value": "60", 103 | "item_type": "int", 104 | "Item_desc": "Time between difficulty retargets" 105 | }, 106 | { 107 | "id": 15, 108 | "module": "daemon", 109 | "item": "address", 110 | "item_value": "127.0.0.1", 111 | "item_type": "string", 112 | "Item_desc": "Monero Daemon RPC IP" 113 | }, 114 | { 115 | "id": 16, 116 | "module": "daemon", 117 | "item": "port", 118 | "item_value": "18081", 119 | "item_type": "int", 120 | "Item_desc": "Monero Daemon RPC Port" 121 | }, 122 | { 123 | "id": 17, 124 | "module": "wallet", 125 | "item": "address", 126 | "item_value": "127.0.0.1", 127 | "item_type": "string", 128 | "Item_desc": "Monero Daemon RPC Wallet IP" 129 | }, 130 | { 131 | "id": 18, 132 | "module": "wallet", 133 | "item": "port", 134 | "item_value": "37458", 135 | "item_type": "int", 136 | "Item_desc": "Monero Daemon RPC Wallet Port" 137 | }, 138 | { 139 | "id": 21, 140 | "module": "rpc", 141 | "item": "https", 142 | "item_value": "false", 143 | "item_type": "bool", 144 | "Item_desc": "Enable RPC over SSL" 145 | }, 146 | { 147 | "id": 22, 148 | "module": "pool", 149 | "item": "maxDifficulty", 150 | "item_value": "500000", 151 | "item_type": "int", 152 | "Item_desc": "Maximum difficulty for VarDiff" 153 | }, 154 | { 155 | "id": 23, 156 | "module": "pool", 157 | "item": "minDifficulty", 158 | "item_value": "100", 159 | "item_type": "int", 160 | "Item_desc": "Minimum difficulty for VarDiff" 161 | }, 162 | { 163 | "id": 24, 164 | "module": "pool", 165 | "item": "varDiffVariance", 166 | "item_value": "20", 167 | "item_type": "int", 168 | "Item_desc": "Percentage out of the target time that difficulty changes" 169 | }, 170 | { 171 | "id": 25, 172 | "module": "pool", 173 | "item": "varDiffMaxChange", 174 | "item_value": "30", 175 | "item_type": "int", 176 | "Item_desc": "Percentage amount that the difficulty may change" 177 | }, 178 | { 179 | "id": 27, 180 | "module": "payout", 181 | "item": "btcFee", 182 | "item_value": "1.5", 183 | "item_type": "float", 184 | "Item_desc": "Fee charged for auto withdrawl via BTC" 185 | }, 186 | { 187 | "id": 28, 188 | "module": "payout", 189 | "item": "ppsFee", 190 | "item_value": "6.5", 191 | "item_type": "float", 192 | "Item_desc": "Fee charged for usage of the PPS pool" 193 | }, 194 | { 195 | "id": 29, 196 | "module": "payout", 197 | "item": "pplnsFee", 198 | "item_value": ".6", 199 | "item_type": "float", 200 | "Item_desc": "Fee charged for the usage of the PPLNS pool" 201 | }, 202 | { 203 | "id": 30, 204 | "module": "payout", 205 | "item": "propFee", 206 | "item_value": ".7", 207 | "item_type": "float", 208 | "Item_desc": "Fee charged for the usage of the proportial pool" 209 | }, 210 | { 211 | "id": 31, 212 | "module": "payout", 213 | "item": "soloFee", 214 | "item_value": ".4", 215 | "item_type": "float", 216 | "Item_desc": "Fee charged for usage of the solo mining pool" 217 | }, 218 | { 219 | "id": 32, 220 | "module": "payout", 221 | "item": "exchangeMin", 222 | "item_value": "5", 223 | "item_type": "float", 224 | "Item_desc": "Minimum XMR balance for payout to exchange/payment ID" 225 | }, 226 | { 227 | "id": 33, 228 | "module": "payout", 229 | "item": "walletMin", 230 | "item_value": ".3", 231 | "item_type": "float", 232 | "Item_desc": "Minimum XMR balance for payout to personal wallet" 233 | }, 234 | { 235 | "id": 34, 236 | "module": "payout", 237 | "item": "devDonation", 238 | "item_value": "5", 239 | "item_type": "float", 240 | "Item_desc": "Donation to XMR core development" 241 | }, 242 | { 243 | "id": 35, 244 | "module": "payout", 245 | "item": "poolDevDonation", 246 | "item_value": "0", 247 | "item_type": "float", 248 | "Item_desc": "Donation to pool developer" 249 | }, 250 | { 251 | "id": 36, 252 | "module": "payout", 253 | "item": "denom", 254 | "item_value": ".000001", 255 | "item_type": "float", 256 | "Item_desc": "Minimum balance that will be paid out to." 257 | }, 258 | { 259 | "id": 37, 260 | "module": "payout", 261 | "item": "blocksRequired", 262 | "item_value": "60", 263 | "item_type": "int", 264 | "Item_desc": "Blocks required to validate a payout before it's performed." 265 | }, 266 | { 267 | "id": 38, 268 | "module": "general", 269 | "item": "sigDivisor", 270 | "item_value": "1000000000000", 271 | "item_type": "int", 272 | "Item_desc": "Divisor for turning coin into human readable amounts " 273 | }, 274 | { 275 | "id": 39, 276 | "module": "payout", 277 | "item": "feeAddress", 278 | "item_value": "", 279 | "item_type": "string", 280 | "Item_desc": "Address that pool fees are sent to." 281 | }, 282 | { 283 | "id": 40, 284 | "module": "payout", 285 | "item": "feesForTXN", 286 | "item_value": "10", 287 | "item_type": "int", 288 | "Item_desc": "Amount of XMR that is left from the fees to pay miner fees." 289 | }, 290 | { 291 | "id": 41, 292 | "module": "payout", 293 | "item": "maxTxnValue", 294 | "item_value": "250", 295 | "item_type": "int", 296 | "Item_desc": "Maximum amount of XMR to send in a single transaction" 297 | }, 298 | { 299 | "id": 42, 300 | "module": "payout", 301 | "item": "shapeshiftPair", 302 | "item_value": "xmr_btc", 303 | "item_type": "string", 304 | "Item_desc": "Pair to use in all shapeshift lookups for auto BTC payout" 305 | }, 306 | { 307 | "id": 43, 308 | "module": "general", 309 | "item": "coinCode", 310 | "item_value": "XMR", 311 | "item_type": "string", 312 | "Item_desc": "Coincode to be loaded up w/ the shapeshift getcoins argument." 313 | }, 314 | { 315 | "id": 44, 316 | "module": "general", 317 | "item": "allowBitcoin", 318 | "item_value": "true", 319 | "item_type": "bool", 320 | "Item_desc": "Allow the pool to auto-payout to BTC via ShapeShift" 321 | }, 322 | { 323 | "id": 45, 324 | "module": "payout", 325 | "item": "exchangeRate", 326 | "item_value": "1217200", 327 | "item_type": "float", 328 | "Item_desc": "Current exchange rate" 329 | }, 330 | { 331 | "id": 46, 332 | "module": "payout", 333 | "item": "bestExchange", 334 | "item_value": "xmrto", 335 | "item_type": "string", 336 | "Item_desc": "Current best exchange" 337 | }, 338 | { 339 | "id": 47, 340 | "module": "payout", 341 | "item": "mixIn", 342 | "item_value": "4", 343 | "item_type": "int", 344 | "Item_desc": "Mixin count for coins that support such things." 345 | }, 346 | { 347 | "id": 48, 348 | "module": "pool", 349 | "item": "geoDNS", 350 | "item_value": "", 351 | "item_type": "string", 352 | "Item_desc": "geoDNS enabled address for the pool." 353 | }, 354 | { 355 | "id": 49, 356 | "module": "general", 357 | "item": "statsBufferLength", 358 | "item_value": "120", 359 | "item_type": "int", 360 | "Item_desc": "Number of items to be cached in the stats buffers." 361 | }, 362 | { 363 | "id": 50, 364 | "module": "api", 365 | "item": "authKey", 366 | "item_value": "", 367 | "item_type": "string", 368 | "Item_desc": "Auth key sent with all Websocket frames for validation" 369 | }, 370 | { 371 | "id": 51, 372 | "module": "general", 373 | "item": "mailgunKey", 374 | "item_value": "", 375 | "item_type": "string", 376 | "Item_desc": "MailGun API Key for notification" 377 | }, 378 | { 379 | "id": 52, 380 | "module": "general", 381 | "item": "mailgunURL", 382 | "item_value": "", 383 | "item_type": "string", 384 | "Item_desc": "MailGun URL for notifications" 385 | }, 386 | { 387 | "id": 53, 388 | "module": "general", 389 | "item": "emailFrom", 390 | "item_value": "", 391 | "item_type": "string", 392 | "Item_desc": "From address for the notification emails" 393 | }, 394 | { 395 | "id": 54, 396 | "module": "pps", 397 | "item": "enable", 398 | "item_value": "false", 399 | "item_type": "bool", 400 | "Item_desc": "Enable PPS or not" 401 | }, 402 | { 403 | "id": 55, 404 | "module": "pplns", 405 | "item": "shareMulti", 406 | "item_value": "2", 407 | "item_type": "int", 408 | "Item_desc": "Multiply this times difficulty to set the N in PPLNS" 409 | }, 410 | { 411 | "id": 56, 412 | "module": "pplns", 413 | "item": "shareMultiLog", 414 | "item_value": "3", 415 | "item_type": "int", 416 | "Item_desc": "How many times the difficulty of the current block do we keep in shares before clearing them out" 417 | }, 418 | { 419 | "id": 57, 420 | "module": "general", 421 | "item": "blockCleaner", 422 | "item_value": "true", 423 | "item_type": "bool", 424 | "Item_desc": "Enable the deletion of blocks or not." 425 | }, 426 | { 427 | "id": 58, 428 | "module": "api", 429 | "item": "secKey", 430 | "item_value": "", 431 | "item_type": "string", 432 | "Item_desc": "HMAC key for Passwords. JWT Secret Key" 433 | }, 434 | { 435 | "id": 59, 436 | "module": "payout", 437 | "item": "feeSlewAmount", 438 | "item_value": ".011", 439 | "item_type": "float", 440 | "Item_desc": "Amount to charge for the txn fee" 441 | }, 442 | { 443 | "id": 60, 444 | "module": "payout", 445 | "item": "feeSlewEnd", 446 | "item_value": "4", 447 | "item_type": "float", 448 | "Item_desc": "Value at which txn fee amount drops to 0" 449 | }, 450 | { 451 | "id": 61, 452 | "module": "general", 453 | "item": "testnet", 454 | "item_value": "false", 455 | "item_type": "bool", 456 | "Item_desc": "Does this pool use testnet?" 457 | }, 458 | { 459 | "id": 62, 460 | "module": "pplns", 461 | "item": "enable", 462 | "item_value": "true", 463 | "item_type": "bool", 464 | "Item_desc": "Enable PPLNS on the pool." 465 | }, 466 | { 467 | "id": 63, 468 | "module": "solo", 469 | "item": "enable", 470 | "item_value": "true", 471 | "item_type": "bool", 472 | "Item_desc": "Enable SOLO mining on the pool" 473 | }, 474 | { 475 | "id": 64, 476 | "module": "general", 477 | "item": "adminEmail", 478 | "item_value": "", 479 | "item_type": "string", 480 | "Item_desc": "Admin e-mail to send e-mails to when something isn't working right." 481 | }, 482 | { 483 | "id": 65, 484 | "module": "payout", 485 | "item": "rpcPasswordEnabled", 486 | "item_value": "false", 487 | "item_type": "bool", 488 | "Item_desc": "Does the wallet use a RPC password?" 489 | }, 490 | { 491 | "id": 66, 492 | "module": "payout", 493 | "item": "rpcPasswordPath", 494 | "item_value": "", 495 | "item_type": "string", 496 | "Item_desc": "Path and file for the RPC password file location" 497 | }, 498 | { 499 | "id": 67, 500 | "module": "payout", 501 | "item": "maxPaymentTxns", 502 | "item_value": "5", 503 | "item_type": "int", 504 | "Item_desc": "Maximum number of transactions in a single payment" 505 | }, 506 | { 507 | "id": 68, 508 | "module": "general", 509 | "item": "shareHost", 510 | "item_value": "", 511 | "item_type": "string", 512 | "Item_desc": "Host that receives share information" 513 | }, 514 | { 515 | "id": 70, 516 | "module": "email", 517 | "item": "workerNotHashingBody", 518 | "item_value": "Hello,\n\nYour worker: %(worker)s has stopped submitting hashes at: %(timestamp)s UTC\n\nThank you,\n%(poolEmailSig)s", 519 | "item_type": "string", 520 | "Item_desc": "Email sent to the miner when their worker stops hashing" 521 | }, 522 | { 523 | "id": 71, 524 | "module": "email", 525 | "item": "workerNotHashingSubject", 526 | "item_value": "Worker %(worker)s stopped hashing", 527 | "item_type": "string", 528 | "Item_desc": "Subject of email sent to miner when worker stops hashing" 529 | }, 530 | { 531 | "id": 72, 532 | "module": "general", 533 | "item": "emailSig", 534 | "item_value": "NodeJS-Pool Administration Team", 535 | "item_type": "string", 536 | "Item_desc": "Signature line for the emails." 537 | }, 538 | { 539 | "id": 73, 540 | "module": "payout", 541 | "item": "timer", 542 | "item_value": "120", 543 | "item_type": "int", 544 | "Item_desc": "Number of minutes between main payment daemon cycles" 545 | }, 546 | { 547 | "id": 74, 548 | "module": "payout", 549 | "item": "timerRetry", 550 | "item_value": "25", 551 | "item_type": "int", 552 | "Item_desc": "Number of minutes between payment daemon retrying due to not enough funds" 553 | }, 554 | { 555 | "id": 75, 556 | "module": "payout", 557 | "item": "priority", 558 | "item_value": "0", 559 | "item_type": "int", 560 | "Item_desc": "Payout priority setting. 0 = use default (4x fee); 1 = low prio (1x fee)" 561 | }, 562 | { 563 | "id": 76, 564 | "module": "payout", 565 | "item": "fee", 566 | "item_value": "10000000000", 567 | "item_type": "int", 568 | "Item_desc": "Atomic units of coin to use ass a fee" 569 | }, 570 | { 571 | "id": 77, 572 | "module": "payout", 573 | "item": "unlock_time", 574 | "item_value": "0", 575 | "item_type": "int", 576 | "Item_desc": "Number of blocks assumed before the payout unlocks." 577 | } 578 | ] 579 | -------------------------------------------------------------------------------- /sql_sync/sql_sync.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | let mysql = require("promise-mysql"); 3 | let fs = require("fs"); 4 | let config = fs.readFileSync("../config.json"); 5 | let sql_schema = fs.readFileSync("config_entries.json"); 6 | let async = require("async"); 7 | 8 | global.config = JSON.parse(config); 9 | global.mysql = mysql.createPool(global.config.mysql); 10 | global.schema = JSON.parse(sql_schema); 11 | 12 | // Config Table Layout 13 | // . 14 | 15 | let loopCount = 0; 16 | let updatedCount = 0; 17 | async.eachSeries(global.schema, function(entry, callback){ 18 | global.mysql.query("SELECT * FROM config WHERE module = ? AND item = ?", [entry.module, entry.item]).then(function(rows){ 19 | loopCount += 1; 20 | if (rows.length > 0){ 21 | return callback(); 22 | } 23 | updatedCount += 1; 24 | global.mysql.query("INSERT INTO config (module, item, item_value, item_type, Item_desc) VALUES (?, ?, ?, ?, ?)", [entry.module, entry.item, entry.item_value, entry.item_type, entry.Item_desc]).then(function(){ 25 | return callback(); 26 | }); 27 | }); 28 | }, function(){ 29 | console.log("Updated SQL schema with "+updatedCount+" new rows! Exiting!"); 30 | process.exit(); 31 | }); -------------------------------------------------------------------------------- /tools/blocks.js: -------------------------------------------------------------------------------- 1 | const valid_actions = ['finder', 'stats', 'list']; 2 | let error = 0; 3 | 4 | if (!global.argv.hasOwnProperty('action') || valid_actions.indexOf(global.argv.action) === -1) { 5 | console.error("No action provided to block module."); 6 | console.error("Valid actions: " + valid_actions.join(', ')); 7 | } 8 | 9 | switch (global.argv.action) { 10 | case 'finder': 11 | if (!global.argv.hasOwnProperty('value')) { 12 | console.error('No block provided in value field. Please use --value=blockID'); 13 | error = 1; 14 | break; 15 | } 16 | let blockID = parseInt(global.argv.value); 17 | let block_data = global.database.getBlockByID(blockID); 18 | /* 19 | required string hash = 1; 20 | required int64 difficulty = 2; 21 | required int64 shares = 3; 22 | required int64 timestamp = 4; 23 | required POOLTYPE poolType = 5; 24 | required bool unlocked = 6; 25 | required bool valid = 7; 26 | optional int64 value = 8; 27 | */ 28 | if (!block_data) { 29 | console.error("Invalid blockID provided."); 30 | error = 1; 31 | break; 32 | } 33 | console.log("Data for block: " + blockID + '\n' + 34 | 'Hash: ' + block_data.hash + '\n' + 35 | 'Difficulty: ' + block_data.hash + '\n' + 36 | 'Hashes Required: ' + block_data.hash + '\n' + 37 | 'Find Time: ' + block_data.hash + '\n' + 38 | 'Pool Type: ' + block_data.hash + '\n' + 39 | 'Unlocked: ' + block_data.hash + '\n' + 40 | 'Valid: ' + block_data.hash + '\n' + 41 | 'Value: ' + block_data.hash + '\n'); 42 | } 43 | 44 | process.exit(error); --------------------------------------------------------------------------------