├── .gitignore ├── Dockerfile ├── README.md ├── accessControl_example.json ├── config_example.json ├── install.sh ├── lib ├── Old version │ ├── aeon.js │ ├── cryptonight.js │ ├── cryptonightheavy.js │ ├── cryptonightlightv7.js │ ├── cryptonightv7.js │ ├── forknote.js │ ├── lok.js │ ├── msr.js │ ├── trtl.js │ ├── tube.js │ ├── xhv.js │ └── xtl.js ├── support.js └── xmr.js ├── package.json ├── proxy.js ├── update.sh └── xnpexample.png /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | npm-debug.log 4 | cert* 5 | config.json -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y curl gnupg \ 5 | && curl -fsSL https://deb.nodesource.com/setup_8.x -o /tmp/node_setup.sh \ 6 | && bash /tmp/node_setup.sh \ 7 | && rm /tmp/node_setup.sh \ 8 | && apt-get install -y nodejs git make g++ libboost-dev libboost-system-dev libboost-date-time-dev \ 9 | && git clone https://github.com/MoneroOcean/xmr-node-proxy /xmr-node-proxy \ 10 | && cd /xmr-node-proxy \ 11 | && npm install \ 12 | && cp -n config_example.json config.json \ 13 | && openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.proxy" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 14 | 15 | EXPOSE 8080 8443 3333 16 | 17 | WORKDIR /xmr-node-proxy 18 | CMD ./update.sh && node proxy.js 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xmr-node-proxy 2 | 3 | Donations are for devs (if available). Based on MoneroOcean and Snipa xmr-node-proxy. 4 | 5 | It is advisable to fresh install. For http access, it's view-only but you can still secure access with login in config.json 6 | 7 | ## Proxy upgrade to newer version 8 | - ~/xmr-node-proxy/update.sh or ./update.sh 9 | 10 | ## Feature 11 | - Http access (http://yourIP:8181) 12 | - Failover pools 13 | 14 | ## "algo" values : 15 | cryptonight/ (0, 1, xtl, msr, rto) 16 | cryptonight-light/ (0, 1) 17 | cryptonight-heavy/ (0, xhv, tube) 18 | 19 | ## "blob_type" values : 20 | - cryptonote - Monero forks like Sumokoin, Electroneum, Graft, Aeon, Intense, Tube, Ryo 21 | - cryptonote2 - Masari 22 | - forknote - Some old Bytecoin forks (do not even know which one) 23 | - forknote2 - Bytecoin/Forknote forks like Turtlecoin, Karbowanec 24 | 25 | ## HTTP monitoring by browser 26 | - In config.json 27 | "httpEnable": true, 28 | "httpAddress": "0.0.0.0", 29 | "httpPort": "8181", 30 | - Your proxy IP address for example is 11.22.33.44 31 | - Monitoring your rig by browser on any devices : http://11.22.33.44:8181 (replacing 11.22.33.44 by your proxy's public Internet address) 32 | ![alt text](https://raw.githubusercontent.com/bobbieltd/xmr-node-proxy/master/xnpexample.png) 33 | 34 | ## HTTP password access 35 | - In config.json if httpUser or httpPass is not empty, http access will be secured 36 | "httpUser": "admin", 37 | "httpPass": "admin", 38 | 39 | ## Balancing with backup pools 40 | 1. Specify at least one main pool with non zero share and "default: true". Sum of all non zero pool shares should be equal to 100 (percent). 41 | 42 | 2. There should be one pool with "default: true" (the last one will override previous ones with "default: true"). Default pool means pool that is used 43 | for all initial miner connections via proxy. 44 | 45 | 3. You can use pools with zero share as backup pools. They will be only used if all non zero share pools became down. 46 | 47 | 4. You should select pool port with difficulty that is close to hashrate of all of your miners multiplied by 10. 48 | 49 | 5. Proxy ports should have difficulty close to your individual miner hashrate multiplied by 10. 50 | 51 | ## Setup Instructions 52 | 53 | Based on a clean Ubuntu 16.04 LTS minimal install. 54 | Very useful and thoroughful guide to setup xmr-node-proxy on free tier Amazon AWS from MO : https://moneroocean.blogspot.com/2017/10/setup-of-xmr-node-proxy-on-free-tier.html 55 | 56 | ## Deployment via Installer 57 | 58 | 1. Create a user 'nodeproxy' and assign a password (or add an SSH key. If you prefer that, you should already know how to do it) 59 | 60 | A. If you have less than 4Gb RAM, you should add swap. Swap is used for compiling the proxy installation but for running proxy 512Mb or 1Gb is enough, very lightweight. 61 | ```bash 62 | sudo fallocate -l 4G /swapfile 63 | sudo chmod 600 /swapfile 64 | sudo mkswap /swapfile 65 | sudo swapon /swapfile 66 | echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab 67 | ``` 68 | 69 | B. Adding new user 70 | ```bash 71 | useradd -d /home/nodeproxy -m -s /bin/bash nodeproxy 72 | passwd nodeproxy 73 | su nodeproxy 74 | ``` 75 | 76 | 2. 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: `nodeproxy ALL=(ALL) NOPASSWD:ALL` 77 | 78 | ```bash 79 | echo "nodeproxy ALL=(ALL) NOPASSWD:ALL" | sudo tee -a /etc/sudoers 80 | ``` 81 | 82 | 3. Log in as the **NON-ROOT USER** you just created and run the [deploy script](https://raw.githubusercontent.com/bobbieltd/xmr-node-proxy/master/install.sh). This is very important! This script will install the proxy to whatever user it's running under! 83 | 84 | ```bash 85 | curl -L https://raw.githubusercontent.com/bobbieltd/xmr-node-proxy/master/install.sh | bash 86 | ``` 87 | 88 | 3. Once it's complete, copy `example_config.json` to `config.json` and edit as desired. 89 | 4. Run: `source ~/.bashrc` This will activate NVM and get things working for the following pm2 steps. 90 | 8. Once you're happy with the settings, go ahead and start all the proxy daemon, commands follow. 91 | 92 | ```shell 93 | cd ~/xmr-node-proxy/ 94 | pm2 start proxy.js --name=proxy --log-date-format="YYYY-MM-DD HH:mm Z" 95 | pm2 save 96 | ``` 97 | You can check the status of your proxy by either issuing 98 | 99 | ``` 100 | pm2 log proxy 101 | ``` 102 | 103 | or using the pm2 monitor 104 | 105 | ``` 106 | pm2 monit 107 | ``` 108 | 109 | ## Known Issues 110 | 111 | VMs with 512Mb or less RAM will need some swap space in order to compile the C extensions for node. Bignum and the CN libraries can chew through some serious memory during compile. In regards to this, one of our users has put together a guide for T2.Micro servers: https://docs.google.com/document/d/1m8E4_pDwKuFo0TnWJaO13LDHqOmbL6YrzyR6FvzqGgU (Credit goes to MayDay30 for his work with this!) 112 | 113 | If not running on an Ubuntu 16.04 system, please make sure your kernel is at least 3.2 or higher, as older versions will not work for this. 114 | 115 | Many smaller VMs come with ulimits set very low. We suggest looking into setting the ulimit higher. In particular, `nofile` (Number of files open) needs to be raised for high-usage instances. Guide : http://posidev.com/blog/2009/06/04/set-ulimit-parameters-on-ubuntu/ 116 | 117 | If your system doesn't have AES-NI, then it will throw an error and utils should be changed .... 118 | 119 | In your `packages.json`, do a `npm install`, and it should pass. 120 | 121 | 122 | ## Performance 123 | 124 | The proxy gains a massive boost over a basic pool by accepting that the majority of the hashes submitted _will_ not be valid (does not exceed the required difficulty of the pool). Due to this, the proxy doesn't bother with attempting to validate the hash state nor value until the share difficulty exceeds the pool difficulty. 125 | 126 | In testing, we've seen AWS t2.micro instances take upwards of 2k connections, while t2.small taking 6k. The proxy is extremely light weight, and while there are more features on the way, it's our goal to keep the proxy as light weight as possible. 127 | 128 | ## Configuration Guidelines 129 | 130 | Please check the [wiki](https://github.com/Snipa22/xmr-node-proxy/wiki/config_review) for information on configuration 131 | 132 | ## Developer Donations 133 | 134 | The proxy is pre-configured for a 1% donation. This is easily toggled inside of it's configuration. If you'd like to make a one time donation, the addresses are as follows: 135 | 136 | * XMR - 44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr 137 | * BTC - 114DGE2jmPb5CP2RGKZn6u6xtccHhZGFmM 138 | 139 | ## Installation/Configuration Assistance 140 | 141 | 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. 142 | 143 | Installation asstiance is 4 XMR, with a 2 XMR deposit, with remainder to be paid on completion. 144 | Configuration assistance is 2 XMR with a 1 XMR deposit, and includes debugging your proxy configurations, ensuring that everything is running, and tuning for your uses/needs. 145 | 146 | SSH access with a sudo-enabled user will be needed for installs, preferably the user that is slated to run the pool. 147 | 148 | Please contact Snipa at: proxy_installs@snipanet.com or via IRC on irc.freenode.net in #monero-pools 149 | 150 | ## Known Working Pools 151 | 152 | * [XMRPool.net](https://xmrpool.net) 153 | * [supportXMR.com](https://supportxmr.com) 154 | * [MoneroOcean.stream](https://moneroocean.stream) 155 | * [xmr.semiPOOL.com](https://xmr.semipool.com) 156 | * [etn.semiPOOL.com](https://etn.semipool.com) 157 | * [aeon.semiPOOL.com](https://aeon.semipool.com) 158 | * [grft.semiPOOL.com](https://grft.semipool.com) 159 | * [tube.semiPOOL.com](https://tube.semipool.com) 160 | * [sumo.semiPOOL.com](https://sumo.semipool.com) 161 | * [krb.semiPOOL.com](https://krb.semipool.com) 162 | * [trtl.semiPOOL.com](https://trtl.semipool.com) 163 | * [lok.semiPOOL.com](https://lok.semipool.com) 164 | * [itnspool.net](https://itnspool.net) 165 | 166 | If you'd like to have your pool added, please make a pull request here, or contact Snipa on IRC! 167 | -------------------------------------------------------------------------------- /accessControl_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "userOne_withoutFixedDiff": "passwordForUser1", 3 | "userTwo_withoutFixedDiff": "passwordForUser2", 4 | "etc": "etc" 5 | } 6 | -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "pools": [ 3 | { 4 | "hostname": "pool.xmr.semipool.com", 5 | "port": 5555, 6 | "ssl": false, 7 | "allowSelfSignedSSL": false, 8 | "share": 100, 9 | "username": "44qJYxdbuqSKarYnDSXB6KLbsH4yR65vpJe3ELLDii9i4ZgKpgQXZYR4AMJxBJbfbKZGWUxZU42QyZSsP4AyZZMbJBCrWr1", 10 | "password": "proxy", 11 | "keepAlive": true, 12 | "algo": "cryptonight/1", 13 | "blob_type": "cryptonote", 14 | "default": true 15 | }, 16 | { 17 | "hostname": "gulf.moneroocean.stream", 18 | "port": 10032, 19 | "ssl": false, 20 | "allowSelfSignedSSL": false, 21 | "share": 100, 22 | "username": "44qJYxdbuqSKarYnDSXB6KLbsH4yR65vpJe3ELLDii9i4ZgKpgQXZYR4AMJxBJbfbKZGWUxZU42QyZSsP4AyZZMbJBCrWr1", 23 | "password": "proxy", 24 | "keepAlive": true, 25 | "algo": "cryptonight/1", 26 | "blob_type": "cryptonote", 27 | "default": true 28 | } 29 | ], 30 | "listeningPorts": [ 31 | { 32 | "port": 80, 33 | "ssl": false, 34 | "diff": 1000 35 | }, 36 | { 37 | "port": 8080, 38 | "ssl": false, 39 | "diff": 5000 40 | }, 41 | { 42 | "port": 8443, 43 | "ssl": true, 44 | "diff": 5000 45 | }, 46 | { 47 | "port": 3333, 48 | "ssl": false, 49 | "diff": 10000 50 | }, 51 | { 52 | "port": 5555, 53 | "ssl": true, 54 | "diff": 5000 55 | }, 56 | { 57 | "port": 7777, 58 | "ssl": false, 59 | "diff": 10000 60 | } 61 | ], 62 | "bindAddress": "0.0.0.0", 63 | "developerShare": 1, 64 | "daemonAddress": "127.0.0.1:18081", 65 | "accessControl": { 66 | "enabled": false, 67 | "controlFile": "accessControl.json" 68 | }, 69 | "httpEnable": false, 70 | "httpAddress": "0.0.0.0", 71 | "httpPort": "8181", 72 | "httpUser": "", 73 | "httpPass": "", 74 | "addressWorkerID": false, 75 | "email" : "reservedfeature@gmail.com-60", 76 | "minerInactivityTime": 120, 77 | "keepOfflineMiners": 0, 78 | "refreshTime": 30, 79 | "theme": "light", 80 | "coinSettings": { 81 | "xmr":{ 82 | "minDiff": 100, 83 | "maxDiff": 300000, 84 | "shareTargetTime": 15 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 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 | 11 | if which yum >/dev/null; then 12 | sudo yum -y update 13 | sudo yum -y upgrade 14 | sudo yum -y install git curl make gcc-c++ python-virtualenv boost-devel boost-system-devel boost-date-time-devel 15 | else 16 | sudo apt-get update 17 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y upgrade 18 | sudo DEBIAN_FRONTEND=noninteractive apt-get -y install git curl make g++ python-virtualenv libboost-dev libboost-system-dev libboost-date-time-dev 19 | fi 20 | cd ~ 21 | git clone https://github.com/bobbieltd/xmr-node-proxy 22 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash 23 | source ~/.nvm/nvm.sh 24 | nvm install v8.12.0 25 | nvm alias default v8.12.0 26 | cd ~/xmr-node-proxy 27 | npm install || exit 1 28 | npm install -g pm2 29 | cp config_example.json config.json 30 | sudo setcap 'cap_net_bind_service=+ep' `which node` 31 | openssl req -subj "/C=IT/ST=Pool/L=Daemon/O=Mining Pool/CN=mining.proxy" -newkey rsa:2048 -nodes -keyout cert.key -x509 -out cert.pem -days 36500 32 | cd ~ 33 | pm2 status 34 | sudo env PATH=$PATH:`pwd`/.nvm/versions/node/v8.12.0/bin `pwd`/.nvm/versions/node/v8.12.0/lib/node_modules/pm2/bin/pm2 startup systemd -u $CURUSER --hp `pwd` 35 | sudo chown -R $CURUSER. ~/.pm2 36 | echo "Installing pm2-logrotate in the background!" 37 | pm2 install pm2-logrotate 38 | echo "You're setup with a shiny new proxy! Now, do 'source ~/.bashrc' command, go configure it and have fun." 39 | -------------------------------------------------------------------------------- /lib/Old version/aeon.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.difficulty = template.difficulty; 55 | this.height = template.height; 56 | this.reservedOffset = template.reserved_offset; 57 | this.workerOffset = template.worker_offset; // clientNonceLocation 58 | this.targetDiff = template.target_diff; 59 | this.targetHex = template.target_diff_hex; 60 | this.buffer = new Buffer(this.blob, 'hex'); 61 | this.previousHash = new Buffer(32); 62 | this.workerNonce = 0; 63 | this.solo = false; 64 | if (typeof(this.workerOffset) === 'undefined') { 65 | this.solo = true; 66 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 67 | this.buffer.copy(this.previousHash, 0, 7, 39); 68 | } 69 | this.nextBlob = function () { 70 | if (this.solo) { 71 | // This is running in solo mode. 72 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 73 | } else { 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 75 | } 76 | return cnUtil.convert_blob(this.buffer).toString('hex'); 77 | }; 78 | } 79 | 80 | function MasterBlockTemplate(template) { 81 | /* 82 | We receive something identical to the result portions of the monero GBT call. 83 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 84 | You know. Just in case amirite? 85 | */ 86 | this.blob = template.blocktemplate_blob; 87 | this.blob_type = parse_blob_type(template.blob_type); 88 | this.difficulty = template.difficulty; 89 | this.height = template.height; 90 | this.reservedOffset = template.reserved_offset; // reserveOffset 91 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 92 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 93 | this.targetDiff = template.target_diff; 94 | this.targetHex = template.target_diff_hex; 95 | this.buffer = new Buffer(this.blob, 'hex'); 96 | this.previousHash = new Buffer(32); 97 | this.job_id = template.job_id; 98 | this.workerNonce = 0; 99 | this.poolNonce = 0; 100 | this.solo = false; 101 | if (typeof(this.workerOffset) === 'undefined') { 102 | this.solo = true; 103 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 104 | this.buffer.copy(this.previousHash, 0, 7, 39); 105 | } 106 | this.blobForWorker = function () { 107 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 108 | return this.buffer.toString('hex'); 109 | }; 110 | } 111 | 112 | function getJob(miner, activeBlockTemplate, bashCache) { 113 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 114 | return miner.cachedJob; 115 | } 116 | 117 | let blob = activeBlockTemplate.nextBlob(); 118 | let target = getTargetHex(miner); 119 | miner.lastBlockHeight = activeBlockTemplate.height; 120 | 121 | let newJob = { 122 | id: crypto.pseudoRandomBytes(21).toString('base64'), 123 | extraNonce: activeBlockTemplate.workerNonce, 124 | height: activeBlockTemplate.height, 125 | difficulty: miner.difficulty, 126 | diffHex: miner.diffHex, 127 | submissions: [], 128 | templateID: activeBlockTemplate.id 129 | }; 130 | 131 | miner.validJobs.enq(newJob); 132 | miner.cachedJob = { 133 | blob: blob, 134 | job_id: newJob.id, 135 | target: target, 136 | id: miner.id 137 | }; 138 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 139 | miner.cachedJob.variant = activeBlockTemplate.variant; 140 | } 141 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 142 | miner.cachedJob.algo = activeBlockTemplate.algo; 143 | } 144 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 145 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 146 | } 147 | return miner.cachedJob; 148 | } 149 | 150 | function getMasterJob(pool, workerID) { 151 | let activeBlockTemplate = pool.activeBlocktemplate; 152 | let btBlob = activeBlockTemplate.blobForWorker(); 153 | let workerData = { 154 | id: crypto.pseudoRandomBytes(21).toString('base64'), 155 | blocktemplate_blob: btBlob, 156 | blob_type: activeBlockTemplate.blob_type, 157 | difficulty: activeBlockTemplate.difficulty, 158 | height: activeBlockTemplate.height, 159 | reserved_offset: activeBlockTemplate.reservedOffset, 160 | worker_offset: activeBlockTemplate.workerOffset, 161 | target_diff: activeBlockTemplate.targetDiff, 162 | target_diff_hex: activeBlockTemplate.targetHex 163 | }; 164 | let localData = { 165 | id: workerData.id, 166 | masterJobID: activeBlockTemplate.job_id, 167 | poolNonce: activeBlockTemplate.poolNonce 168 | }; 169 | if (!(workerID in pool.poolJobs)) { 170 | pool.poolJobs[workerID] = support.circularBuffer(4); 171 | } 172 | pool.poolJobs[workerID].enq(localData); 173 | return workerData; 174 | } 175 | 176 | function getTargetHex(miner) { 177 | if (miner.newDiff) { 178 | miner.difficulty = miner.newDiff; 179 | miner.newDiff = null; 180 | } 181 | let padded = Buffer.alloc(32); 182 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 183 | diffBuff.copy(padded, 32 - diffBuff.length); 184 | 185 | let buff = padded.slice(0, 4); 186 | let buffArray = buff.toByteArray().reverse(); 187 | let buffReversed = new Buffer(buffArray); 188 | miner.target = buffReversed.readUInt32BE(0); 189 | return buffReversed.toString('hex'); 190 | } 191 | 192 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 193 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 194 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 195 | const VER_SHARES_PERIOD = 5; 196 | let verified_share_start_period; 197 | let verified_share_num; 198 | 199 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 200 | let template = new Buffer(blockTemplate.buffer.length); 201 | blockTemplate.buffer.copy(template); 202 | if (blockTemplate.solo) { 203 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 204 | } else { 205 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 206 | } 207 | 208 | let hash = new Buffer(resultHash, 'hex'); 209 | let hashArray = hash.toByteArray().reverse(); 210 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 211 | let hashDiff = baseDiff.div(hashNum); 212 | 213 | if (hashDiff.ge(blockTemplate.targetDiff)) { 214 | let time_now = Date.now(); 215 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 216 | verified_share_num = 0; 217 | verified_share_start_period = time_now; 218 | } 219 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 220 | // Validate share with CN hash, then if valid, blast it up to the master. 221 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 222 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 223 | hash = multiHashing.cryptonight_light(convertedBlob, 1); 224 | if (hash.toString('hex') !== resultHash) { 225 | console.error(global.threadName + "Bad share from miner " + miner.logString); 226 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 227 | return false; 228 | } 229 | } else { 230 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 231 | } 232 | miner.blocks += 1; 233 | process.send({ 234 | type: 'shareFind', 235 | host: miner.pool, 236 | data: { 237 | btID: blockTemplate.id, 238 | nonce: nonce, 239 | resultHash: resultHash, 240 | workerNonce: job.extraNonce 241 | } 242 | }); 243 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 244 | } 245 | else if (hashDiff.lt(job.difficulty)) { 246 | process.send({type: 'invalidShare'}); 247 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 248 | miner.identifier + " IP: " + miner.ipAddress); 249 | return false; 250 | } 251 | miner.shares += 1; 252 | miner.hashes += job.difficulty; 253 | return true; 254 | } 255 | 256 | let devPool = { 257 | "hostname": "donation.hub.semipool.com", 258 | "port": 3333, 259 | "ssl": false, 260 | "share": 0, 261 | "username": "WmsEg3RuUKCcEvFBtXcqRnGYfiqGJLP1FGBYiNMgrcdUjZ8iMcUn2tdcz59T89inWr9Vae4APBNf7Bg2DReFP5jr23SQqaDMT", 262 | "password": "MoneroOcean address", 263 | "keepAlive": true, 264 | "coin": "aeon", 265 | "default": false, 266 | "devPool": true 267 | }; 268 | 269 | module.exports = function () { 270 | return { 271 | devPool: devPool, 272 | hashSync: multiHashing.cryptonight_light, 273 | hashAsync: multiHashing.CNLAsync, 274 | blockHeightCheck: blockHeightCheck, 275 | getRemoteNodes: getRemoteNodes, 276 | BlockTemplate: BlockTemplate, 277 | getJob: getJob, 278 | processShare: processShare, 279 | MasterBlockTemplate: MasterBlockTemplate, 280 | getMasterJob: getMasterJob 281 | }; 282 | }; 283 | -------------------------------------------------------------------------------- /lib/Old version/cryptonight.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = parse_blob_type(template.blob_type); 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight(convertedBlob, 0); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "donation.hub.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "Sumoo1DGS7c9LEKZNipsiDEqRzaUB3ws7YHfUiiZpx9SQDhdYGEEbZjRET26ewuYEWAZ8uKrz6vpUZkEVY7mDCZyGnQhkLpxKmy", 269 | "password": "MoneroOcean address", 270 | "keepAlive": true, 271 | "coin": "cryptonight", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight, 280 | hashAsync: multiHashing.CNAsync, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; 290 | -------------------------------------------------------------------------------- /lib/Old version/cryptonightheavy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('semipool-heavy-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = parse_blob_type(template.blob_type); 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight_heavy(convertedBlob); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "pool.loki.hashvault.pro", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "LA1pdM13aFQesUvMTBNVtYWrS4KVe69pR9kw73W9i55DJExnzHjLnfZXuYi9jSuReYirehDatKNZ8D56rs1TZ6prBQVAHwe", 269 | "password": "BobbieLtd address", 270 | "keepAlive": true, 271 | "coin": "cryptonightheavy", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight_heavy, 280 | hashAsync: multiHashing.cryptonight_heavy, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; -------------------------------------------------------------------------------- /lib/Old version/cryptonightlightv7.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.difficulty = template.difficulty; 54 | this.height = template.height; 55 | this.reservedOffset = template.reserved_offset; 56 | this.workerOffset = template.worker_offset; // clientNonceLocation 57 | this.targetDiff = template.target_diff; 58 | this.targetHex = template.target_diff_hex; 59 | this.buffer = new Buffer(this.blob, 'hex'); 60 | this.previousHash = new Buffer(32); 61 | this.workerNonce = 0; 62 | this.solo = false; 63 | if (typeof(this.workerOffset) === 'undefined') { 64 | this.solo = true; 65 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 66 | this.buffer.copy(this.previousHash, 0, 7, 39); 67 | } 68 | this.nextBlob = function () { 69 | if (this.solo) { 70 | // This is running in solo mode. 71 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 72 | } else { 73 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 74 | } 75 | return cnUtil.convert_blob(this.buffer).toString('hex'); 76 | }; 77 | } 78 | 79 | function MasterBlockTemplate(template) { 80 | /* 81 | We receive something identical to the result portions of the monero GBT call. 82 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 83 | You know. Just in case amirite? 84 | */ 85 | this.blob = template.blocktemplate_blob; 86 | this.difficulty = template.difficulty; 87 | this.height = template.height; 88 | this.reservedOffset = template.reserved_offset; // reserveOffset 89 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 90 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 91 | this.targetDiff = template.target_diff; 92 | this.targetHex = template.target_diff_hex; 93 | this.buffer = new Buffer(this.blob, 'hex'); 94 | this.previousHash = new Buffer(32); 95 | this.job_id = template.job_id; 96 | this.workerNonce = 0; 97 | this.poolNonce = 0; 98 | this.solo = false; 99 | if (typeof(this.workerOffset) === 'undefined') { 100 | this.solo = true; 101 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 102 | this.buffer.copy(this.previousHash, 0, 7, 39); 103 | } 104 | this.blobForWorker = function () { 105 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 106 | return this.buffer.toString('hex'); 107 | }; 108 | } 109 | 110 | function getJob(miner, activeBlockTemplate, bashCache) { 111 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 112 | return miner.cachedJob; 113 | } 114 | 115 | let blob = activeBlockTemplate.nextBlob(); 116 | let target = getTargetHex(miner); 117 | miner.lastBlockHeight = activeBlockTemplate.height; 118 | 119 | let newJob = { 120 | id: crypto.pseudoRandomBytes(21).toString('base64'), 121 | extraNonce: activeBlockTemplate.workerNonce, 122 | height: activeBlockTemplate.height, 123 | difficulty: miner.difficulty, 124 | diffHex: miner.diffHex, 125 | submissions: [], 126 | templateID: activeBlockTemplate.id 127 | }; 128 | 129 | miner.validJobs.enq(newJob); 130 | miner.cachedJob = { 131 | blob: blob, 132 | job_id: newJob.id, 133 | target: target, 134 | id: miner.id 135 | }; 136 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 137 | miner.cachedJob.variant = activeBlockTemplate.variant; 138 | } 139 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 140 | miner.cachedJob.algo = activeBlockTemplate.algo; 141 | } 142 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 143 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 144 | } 145 | return miner.cachedJob; 146 | } 147 | 148 | function getMasterJob(pool, workerID) { 149 | let activeBlockTemplate = pool.activeBlocktemplate; 150 | let btBlob = activeBlockTemplate.blobForWorker(); 151 | let workerData = { 152 | id: crypto.pseudoRandomBytes(21).toString('base64'), 153 | blocktemplate_blob: btBlob, 154 | difficulty: activeBlockTemplate.difficulty, 155 | height: activeBlockTemplate.height, 156 | reserved_offset: activeBlockTemplate.reservedOffset, 157 | worker_offset: activeBlockTemplate.workerOffset, 158 | target_diff: activeBlockTemplate.targetDiff, 159 | target_diff_hex: activeBlockTemplate.targetHex 160 | }; 161 | let localData = { 162 | id: workerData.id, 163 | masterJobID: activeBlockTemplate.job_id, 164 | poolNonce: activeBlockTemplate.poolNonce 165 | }; 166 | if (!(workerID in pool.poolJobs)) { 167 | pool.poolJobs[workerID] = support.circularBuffer(4); 168 | } 169 | pool.poolJobs[workerID].enq(localData); 170 | return workerData; 171 | } 172 | 173 | function getTargetHex(miner) { 174 | if (miner.newDiff) { 175 | miner.difficulty = miner.newDiff; 176 | miner.newDiff = null; 177 | } 178 | let padded = Buffer.alloc(32); 179 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 180 | diffBuff.copy(padded, 32 - diffBuff.length); 181 | 182 | let buff = padded.slice(0, 4); 183 | let buffArray = buff.toByteArray().reverse(); 184 | let buffReversed = new Buffer(buffArray); 185 | miner.target = buffReversed.readUInt32BE(0); 186 | return buffReversed.toString('hex'); 187 | } 188 | 189 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 190 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 191 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 192 | const VER_SHARES_PERIOD = 5; 193 | let verified_share_start_period; 194 | let verified_share_num; 195 | 196 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 197 | let template = new Buffer(blockTemplate.buffer.length); 198 | blockTemplate.buffer.copy(template); 199 | if (blockTemplate.solo) { 200 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 201 | } else { 202 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 203 | } 204 | 205 | let hash = new Buffer(resultHash, 'hex'); 206 | let hashArray = hash.toByteArray().reverse(); 207 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 208 | let hashDiff = baseDiff.div(hashNum); 209 | 210 | if (hashDiff.ge(blockTemplate.targetDiff)) { 211 | let time_now = Date.now(); 212 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 213 | verified_share_num = 0; 214 | verified_share_start_period = time_now; 215 | } 216 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 217 | // Validate share with CN hash, then if valid, blast it up to the master. 218 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 219 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 220 | hash = multiHashing.cryptonight(convertedBlob, 1); 221 | if (hash.toString('hex') !== resultHash) { 222 | console.error(global.threadName + "Bad share from miner " + miner.logString); 223 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 224 | return false; 225 | } 226 | } else { 227 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 228 | } 229 | miner.blocks += 1; 230 | process.send({ 231 | type: 'shareFind', 232 | host: miner.pool, 233 | data: { 234 | btID: blockTemplate.id, 235 | nonce: nonce, 236 | resultHash: resultHash, 237 | workerNonce: job.extraNonce 238 | } 239 | }); 240 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 241 | } 242 | else if (hashDiff.lt(job.difficulty)) { 243 | process.send({type: 'invalidShare'}); 244 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 245 | miner.identifier + " IP: " + miner.ipAddress); 246 | return false; 247 | } 248 | miner.shares += 1; 249 | miner.hashes += job.difficulty; 250 | return true; 251 | } 252 | 253 | let devPool = { 254 | "hostname": "donation.hub.semipool.com", 255 | "port": 3333, 256 | "ssl": false, 257 | "share": 0, 258 | "username": "WmsEg3RuUKCcEvFBtXcqRnGYfiqGJLP1FGBYiNMgrcdUjZ8iMcUn2tdcz59T89inWr9Vae4APBNf7Bg2DReFP5jr23SQqaDMT", 259 | "password": "MoneroOcean address", 260 | "keepAlive": true, 261 | "coin": "aeon", 262 | "default": false, 263 | "devPool": true 264 | }; 265 | 266 | module.exports = function () { 267 | return { 268 | devPool: devPool, 269 | hashSync: multiHashing.cryptonight_light, 270 | hashAsync: multiHashing.CNLAsync, 271 | blockHeightCheck: blockHeightCheck, 272 | getRemoteNodes: getRemoteNodes, 273 | BlockTemplate: BlockTemplate, 274 | getJob: getJob, 275 | processShare: processShare, 276 | MasterBlockTemplate: MasterBlockTemplate, 277 | getMasterJob: getMasterJob 278 | }; 279 | }; 280 | -------------------------------------------------------------------------------- /lib/Old version/cryptonightv7.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = parse_blob_type(template.blob_type); 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight(convertedBlob, 1); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | } 251 | else if (hashDiff.lt(job.difficulty)) { 252 | process.send({type: 'invalidShare'}); 253 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 254 | miner.identifier + " IP: " + miner.ipAddress); 255 | return false; 256 | } 257 | miner.shares += 1; 258 | miner.hashes += job.difficulty; 259 | return true; 260 | } 261 | 262 | let devPool = { 263 | "hostname": "pool.xmr.semipool.com", 264 | "port": 3333, 265 | "ssl": false, 266 | "share": 0, 267 | "username": "44qJYxdbuqSKarYnDSXB6KLbsH4yR65vpJe3ELLDii9i4ZgKpgQXZYR4AMJxBJbfbKZGWUxZU42QyZSsP4AyZZMbJBCrWr1", 268 | "password": "MoneroOcean address", 269 | "keepAlive": true, 270 | "coin": "cryptonightv7", 271 | "default": false, 272 | "devPool": true 273 | }; 274 | 275 | module.exports = function () { 276 | return { 277 | devPool: devPool, 278 | hashSync: multiHashing.cryptonight, 279 | hashAsync: multiHashing.CNAsync, 280 | blockHeightCheck: blockHeightCheck, 281 | getRemoteNodes: getRemoteNodes, 282 | BlockTemplate: BlockTemplate, 283 | getJob: getJob, 284 | processShare: processShare, 285 | MasterBlockTemplate: MasterBlockTemplate, 286 | getMasterJob: getMasterJob 287 | }; 288 | }; 289 | -------------------------------------------------------------------------------- /lib/Old version/forknote.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 2; 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight(convertedBlob, 0); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "donation.hub.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "KhxVjS3iEXJHc8J1yd35rtaATyX9ANiiDWeG9B6yc1ZBHgfmFZxHeMjURSotDbr5CZB6EHwn8LyrS9Emd884fk2QCvqzyAG", 269 | "password": "BobbieLtd address", 270 | "keepAlive": true, 271 | "coin": "forknote", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight, 280 | hashAsync: multiHashing.CNAsync, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; 290 | -------------------------------------------------------------------------------- /lib/Old version/lok.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 0; 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight_heavy(convertedBlob,0); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "pool.loki.hashvault.pro", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "LA1pdM13aFQesUvMTBNVtYWrS4KVe69pR9kw73W9i55DJExnzHjLnfZXuYi9jSuReYirehDatKNZ8D56rs1TZ6prBQVAHwe", 269 | "password": "BobbieLtd address", 270 | "keepAlive": true, 271 | "coin": "lok", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight_heavy, 280 | hashAsync: multiHashing.cryptonight_heavy, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; -------------------------------------------------------------------------------- /lib/Old version/msr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.algo = template.algo; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 3; 90 | this.variant = template.variant; 91 | this.algo = template.algo; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | algo: activeBlockTemplate.algo, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight(convertedBlob, 4); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "pool.msr.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "5kMPcodPYmVYvmsewCMmdu7BjVr8SZjFa7kS35568tuUMZruGys7zpg5cc2DdqzcaP2FdjtdXsJKoTc29dn3MugiG82J7PU", 269 | "password": "Bobbieltd address", 270 | "keepAlive": true, 271 | "coin": "msr", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight, 280 | hashAsync: multiHashing.CNAsync, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; -------------------------------------------------------------------------------- /lib/Old version/trtl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 2; 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight_light(convertedBlob, 1); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "donation.hub.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "TRTLuxFbix6LPwrJZH85YvWbUmh65gJAJigVwPgN2owgM4S8oQTrQbaf93EAWQ47c3ScN9sTsLf3FRM1gFP6Ve1iENQsZiZ6mqd", 269 | "password": "BobbieLtd address", 270 | "keepAlive": true, 271 | "coin": "trtl", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight_light, 280 | hashAsync: multiHashing.CNLAsync, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; 290 | -------------------------------------------------------------------------------- /lib/Old version/tube.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 0; 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex')); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight_heavy(convertedBlob,2); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "donation.hub.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "bxcFC7YbFQPQpzrUYbjsdTTy5F2msDVkUcvj1BCzQEBF5YkfUGRidKpHNFW5eY6xwz5zAQRRsPyK9gtiTsTextXb1sVRvG21W", 269 | "password": "BobbieLtd address", 270 | "keepAlive": true, 271 | "coin": "ipbc", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight_heavy, 280 | hashAsync: multiHashing.cryptonight_heavy, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; 290 | -------------------------------------------------------------------------------- /lib/Old version/xhv.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('semipool-heavy-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.coin = template.coin; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = parse_blob_type(template.blob_type); 90 | this.variant = template.variant; 91 | this.coin = template.coin; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | coin: activeBlockTemplate.coin, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight_heavy(convertedBlob,1); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "pool-xhv.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "hvxxxyXBbn3CjLJhT8Lfgcj8imr3Wue8U3t5LxUAih76BhtGtq3sJJhMtxDLvNCuYgc6BXrgtupfUPdsjZgAmYnX5aStEsgvWg", 269 | "password": "Bobbieltd address", 270 | "keepAlive": true, 271 | "coin": "xhv", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight_heavy, 280 | hashAsync: multiHashing.cryptonight_heavy, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; -------------------------------------------------------------------------------- /lib/Old version/xtl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 31 | } 32 | 33 | function parse_blob_type(blob_type_str) { 34 | if (typeof(blob_type_str) === 'undefined') return 0; 35 | switch (blob_type_str) { 36 | case 'cryptonote': return 0; // Monero 37 | case 'forknote1': return 1; 38 | case 'forknote2': 39 | case 'forknote' : return 2; // Almost all Forknote coins 40 | case 'cryptonote2': return 3; // Masari 41 | } 42 | return 0; 43 | } 44 | 45 | function BlockTemplate(template) { 46 | /* 47 | We receive something identical to the result portions of the monero GBT call. 48 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 49 | You know. Just in case amirite? 50 | */ 51 | this.id = template.id; 52 | this.blob = template.blocktemplate_blob; 53 | this.blob_type = template.blob_type; 54 | this.variant = template.variant; 55 | this.algo = template.algo; 56 | this.difficulty = template.difficulty; 57 | this.height = template.height; 58 | this.reservedOffset = template.reserved_offset; 59 | this.workerOffset = template.worker_offset; // clientNonceLocation 60 | this.targetDiff = template.target_diff; 61 | this.targetHex = template.target_diff_hex; 62 | this.buffer = new Buffer(this.blob, 'hex'); 63 | this.previousHash = new Buffer(32); 64 | this.workerNonce = 0; 65 | this.solo = false; 66 | if (typeof(this.workerOffset) === 'undefined') { 67 | this.solo = true; 68 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 69 | this.buffer.copy(this.previousHash, 0, 7, 39); 70 | } 71 | this.nextBlob = function () { 72 | if (this.solo) { 73 | // This is running in solo mode. 74 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 75 | } else { 76 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 77 | } 78 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 79 | }; 80 | } 81 | 82 | function MasterBlockTemplate(template) { 83 | /* 84 | We receive something identical to the result portions of the monero GBT call. 85 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 86 | You know. Just in case amirite? 87 | */ 88 | this.blob = template.blocktemplate_blob; 89 | this.blob_type = 0; 90 | this.variant = template.variant; 91 | this.algo = template.algo; 92 | this.difficulty = template.difficulty; 93 | this.height = template.height; 94 | this.reservedOffset = template.reserved_offset; // reserveOffset 95 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 96 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 97 | this.targetDiff = template.target_diff; 98 | this.targetHex = template.target_diff_hex; 99 | this.buffer = new Buffer(this.blob, 'hex'); 100 | this.previousHash = new Buffer(32); 101 | this.job_id = template.job_id; 102 | this.workerNonce = 0; 103 | this.poolNonce = 0; 104 | this.solo = false; 105 | if (typeof(this.workerOffset) === 'undefined') { 106 | this.solo = true; 107 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 108 | this.buffer.copy(this.previousHash, 0, 7, 39); 109 | } 110 | this.blobForWorker = function () { 111 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 112 | return this.buffer.toString('hex'); 113 | }; 114 | } 115 | 116 | function getJob(miner, activeBlockTemplate, bashCache) { 117 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 118 | return miner.cachedJob; 119 | } 120 | 121 | let blob = activeBlockTemplate.nextBlob(); 122 | let target = getTargetHex(miner); 123 | miner.lastBlockHeight = activeBlockTemplate.height; 124 | 125 | let newJob = { 126 | id: crypto.pseudoRandomBytes(21).toString('base64'), 127 | extraNonce: activeBlockTemplate.workerNonce, 128 | height: activeBlockTemplate.height, 129 | difficulty: miner.difficulty, 130 | diffHex: miner.diffHex, 131 | submissions: [], 132 | templateID: activeBlockTemplate.id 133 | }; 134 | 135 | miner.validJobs.enq(newJob); 136 | 137 | miner.cachedJob = { 138 | blob: blob, 139 | job_id: newJob.id, 140 | target: target, 141 | id: miner.id 142 | }; 143 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 144 | miner.cachedJob.variant = activeBlockTemplate.variant; 145 | } 146 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 147 | miner.cachedJob.algo = activeBlockTemplate.algo; 148 | } 149 | if (typeof (activeBlockTemplate.extensions) !== 'undefined') { 150 | miner.cachedJob.extensions = activeBlockTemplate.extensions.slice(); 151 | } 152 | return miner.cachedJob; 153 | } 154 | 155 | function getMasterJob(pool, workerID) { 156 | let activeBlockTemplate = pool.activeBlocktemplate; 157 | let btBlob = activeBlockTemplate.blobForWorker(); 158 | let workerData = { 159 | id: crypto.pseudoRandomBytes(21).toString('base64'), 160 | blocktemplate_blob: btBlob, 161 | blob_type: activeBlockTemplate.blob_type, 162 | variant: activeBlockTemplate.variant, 163 | algo: activeBlockTemplate.algo, 164 | difficulty: activeBlockTemplate.difficulty, 165 | height: activeBlockTemplate.height, 166 | reserved_offset: activeBlockTemplate.reservedOffset, 167 | worker_offset: activeBlockTemplate.workerOffset, 168 | target_diff: activeBlockTemplate.targetDiff, 169 | target_diff_hex: activeBlockTemplate.targetHex 170 | }; 171 | let localData = { 172 | id: workerData.id, 173 | masterJobID: activeBlockTemplate.job_id, 174 | poolNonce: activeBlockTemplate.poolNonce 175 | }; 176 | if (!(workerID in pool.poolJobs)) { 177 | pool.poolJobs[workerID] = support.circularBuffer(4); 178 | } 179 | pool.poolJobs[workerID].enq(localData); 180 | return workerData; 181 | } 182 | 183 | function getTargetHex(miner) { 184 | if (miner.newDiff) { 185 | miner.difficulty = miner.newDiff; 186 | miner.newDiff = null; 187 | } 188 | let padded = Buffer.alloc(32); 189 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 190 | diffBuff.copy(padded, 32 - diffBuff.length); 191 | 192 | let buff = padded.slice(0, 4); 193 | let buffArray = buff.toByteArray().reverse(); 194 | let buffReversed = new Buffer(buffArray); 195 | miner.target = buffReversed.readUInt32BE(0); 196 | return buffReversed.toString('hex'); 197 | } 198 | 199 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 200 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 201 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 202 | const VER_SHARES_PERIOD = 5; 203 | let verified_share_start_period; 204 | let verified_share_num; 205 | 206 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 207 | let template = new Buffer(blockTemplate.buffer.length); 208 | blockTemplate.buffer.copy(template); 209 | if (blockTemplate.solo) { 210 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 211 | } else { 212 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 213 | } 214 | 215 | let hash = new Buffer(resultHash, 'hex'); 216 | let hashArray = hash.toByteArray().reverse(); 217 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 218 | let hashDiff = baseDiff.div(hashNum); 219 | 220 | if (hashDiff.ge(blockTemplate.targetDiff)) { 221 | let time_now = Date.now(); 222 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 223 | verified_share_num = 0; 224 | verified_share_start_period = time_now; 225 | } 226 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 227 | // Validate share with CN hash, then if valid, blast it up to the master. 228 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 229 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 230 | hash = multiHashing.cryptonight(convertedBlob, 3); 231 | if (hash.toString('hex') !== resultHash) { 232 | console.error(global.threadName + "Bad share from miner " + miner.logString); 233 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 234 | return false; 235 | } 236 | } else { 237 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload " + miner.logString); 238 | } 239 | miner.blocks += 1; 240 | process.send({ 241 | type: 'shareFind', 242 | host: miner.pool, 243 | data: { 244 | btID: blockTemplate.id, 245 | nonce: nonce, 246 | resultHash: resultHash, 247 | workerNonce: job.extraNonce 248 | } 249 | }); 250 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${miner.pool} pool`); 251 | } 252 | else if (hashDiff.lt(job.difficulty)) { 253 | process.send({type: 'invalidShare'}); 254 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 255 | miner.identifier + " IP: " + miner.ipAddress); 256 | return false; 257 | } 258 | miner.shares += 1; 259 | miner.hashes += job.difficulty; 260 | return true; 261 | } 262 | 263 | let devPool = { 264 | "hostname": "pool.xtl.semipool.com", 265 | "port": 3333, 266 | "ssl": false, 267 | "share": 0, 268 | "username": "Se3bWeAn4pMTKrNrhRMfgUPHhpHKWPvTUF6qEULcU91NN5bi6F7UQtsWVDK8vWsF5RRkvtVMBRCQkKxh9LGBdfQJ2JUa7K1aP", 269 | "password": "Bobbieltd address", 270 | "keepAlive": true, 271 | "coin": "xtl", 272 | "default": false, 273 | "devPool": true 274 | }; 275 | 276 | module.exports = function () { 277 | return { 278 | devPool: devPool, 279 | hashSync: multiHashing.cryptonight, 280 | hashAsync: multiHashing.CNAsync, 281 | blockHeightCheck: blockHeightCheck, 282 | getRemoteNodes: getRemoteNodes, 283 | BlockTemplate: BlockTemplate, 284 | getJob: getJob, 285 | processShare: processShare, 286 | MasterBlockTemplate: MasterBlockTemplate, 287 | getMasterJob: getMasterJob 288 | }; 289 | }; -------------------------------------------------------------------------------- /lib/support.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const CircularBuffer = require('circular-buffer'); 3 | const request = require('request'); 4 | const debug = require('debug')('support'); 5 | const fs = require('fs'); 6 | 7 | function circularBuffer(size) { 8 | let buffer = CircularBuffer(size); 9 | 10 | buffer.sum = function () { 11 | if (this.size() === 0) { 12 | return 1; 13 | } 14 | return this.toarray().reduce(function (a, b) { 15 | return a + b; 16 | }); 17 | }; 18 | 19 | buffer.average = function (lastShareTime) { 20 | if (this.size() === 0) { 21 | return global.config.pool.targetTime * 1.5; 22 | } 23 | let extra_entry = (Date.now() / 1000) - lastShareTime; 24 | return (this.sum() + Math.round(extra_entry)) / (this.size() + 1); 25 | }; 26 | 27 | buffer.clear = function () { 28 | let i = this.size(); 29 | while (i > 0) { 30 | this.deq(); 31 | i = this.size(); 32 | } 33 | }; 34 | 35 | return buffer; 36 | } 37 | 38 | function sendEmail(toAddress, subject, body){ 39 | request.post(global.config.general.mailgunURL + "/messages", { 40 | auth: { 41 | user: 'api', 42 | pass: global.config.general.mailgunKey 43 | }, 44 | form: { 45 | from: global.config.general.emailFrom, 46 | to: toAddress, 47 | subject: subject, 48 | text: body 49 | } 50 | }, function(err, response, body){ 51 | if (!err && response.statusCode === 200) { 52 | console.log("Email sent successfully! Response: " + body); 53 | } else { 54 | console.error("Did not send e-mail successfully! Response: " + body + " Response: "+JSON.stringify(response)); 55 | } 56 | }); 57 | } 58 | 59 | function coinToDecimal(amount) { 60 | return amount / global.config.coin.sigDigits; 61 | } 62 | 63 | function decimalToCoin(amount) { 64 | return Math.round(amount * global.config.coin.sigDigits); 65 | } 66 | 67 | function blockCompare(a, b) { 68 | if (a.height < b.height) { 69 | return 1; 70 | } 71 | 72 | if (a.height > b.height) { 73 | return -1; 74 | } 75 | return 0; 76 | } 77 | 78 | function tsCompare(a, b) { 79 | if (a.ts < b.ts) { 80 | return 1; 81 | } 82 | 83 | if (a.ts > b.ts) { 84 | return -1; 85 | } 86 | return 0; 87 | } 88 | 89 | function currentUnixTimestamp(){ 90 | return + new Date(); 91 | } 92 | 93 | module.exports = function () { 94 | return { 95 | circularBuffer: circularBuffer, 96 | coinToDecimal: coinToDecimal, 97 | decimalToCoin: decimalToCoin, 98 | blockCompare: blockCompare, 99 | sendEmail: sendEmail, 100 | tsCompare: tsCompare, 101 | developerAddy: '44Ldv5GQQhP7K7t3ZBdZjkPA7Kg7dhHwk3ZM3RJqxxrecENSFx27Vq14NAMAd2HBvwEPUVVvydPRLcC69JCZDHLT2X5a4gr', 102 | currentUnixTimestamp: currentUnixTimestamp 103 | }; 104 | }; 105 | -------------------------------------------------------------------------------- /lib/xmr.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const multiHashing = require('cryptonight-hashing'); 3 | const cnUtil = require('cryptoforknote-util'); 4 | const bignum = require('bignum'); 5 | const support = require('./support.js')(); 6 | const crypto = require('crypto'); 7 | 8 | let debug = { 9 | pool: require('debug')('pool'), 10 | diff: require('debug')('diff'), 11 | blocks: require('debug')('blocks'), 12 | shares: require('debug')('shares'), 13 | miners: require('debug')('miners'), 14 | workers: require('debug')('workers') 15 | }; 16 | 17 | let baseDiff = bignum('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 16); 18 | 19 | Buffer.prototype.toByteArray = function () { 20 | return Array.prototype.slice.call(this, 0); 21 | }; 22 | 23 | function blockHeightCheck(nodeList, callback) { 24 | let randomNode = nodeList[Math.floor(Math.random() * nodeList.length)].split(':'); 25 | 26 | } 27 | 28 | function getRemoteNodes() { 29 | let knownNodes = [ 30 | '162.213.38.245:18081', 31 | '116.93.119.79:18081', 32 | '85.204.96.231:18081', 33 | '107.167.87.242:18081', 34 | '107.167.93.58:18081', 35 | '199.231.85.122:18081', 36 | '192.110.160.146:18081' 37 | ]; // Prefill the array with known good nodes for now. Eventually will try to download them via DNS or http. 38 | } 39 | 40 | function parse_blob_type(blob_type_str) { 41 | if (typeof(blob_type_str) === 'undefined') return 0; 42 | switch (blob_type_str) { 43 | case 'cryptonote': return 0; // Monero 44 | case 'forknote1': return 1; 45 | case 'forknote2': return 2; // Almost all Forknote coins 46 | case 'cryptonote2': return 3; // Masari 47 | case 'cryptonote_ryo': return 4; // Ryo 48 | case 'cryptonote_loki': return 5; // Loki 49 | } 50 | return 0; 51 | } 52 | 53 | function parse_algo_variant(algo_str, variant) { 54 | if (typeof(variant) === 'undefined' && typeof(algo_str) === 'undefined') variant = 1; 55 | if (typeof(algo_str) === 'undefined') return variant; 56 | switch (algo_str) { 57 | case 'cn': 58 | case 'cryptonight': 59 | case 'cn-lite': 60 | case 'cryptonight-lite': 61 | case 'cn-heavy': 62 | case 'cryptonight-heavy': 63 | case 'cn/0': 64 | case 'cryptonight/0': 65 | case 'cn-lite/0': 66 | case 'cryptonight-lite/0': return 0; 67 | 68 | case 'cn/1': 69 | case 'cryptonight/1': 70 | case 'cn-lite/1': 71 | case 'cryptonight-lite/1': 72 | case 'cn-heavy/xhv': 73 | case 'cryptonight-heavy/xhv': return 1; 74 | 75 | case 'cn-heavy/tube': 76 | case 'cryptonight-heavy/tube': return 2; 77 | 78 | case 'cn/xtl': 79 | case 'cryptonight/xtl': return 3; 80 | 81 | case 'cn/msr': 82 | case 'cryptonight/msr': return 4; 83 | 84 | case 'cn/xao': 85 | case 'cryptonight/xao': return 6; 86 | 87 | case 'cn/rto': 88 | case 'cryptonight/rto': return 7; 89 | 90 | case 'cn/2': 91 | case 'cryptonight/2': return 8; 92 | } 93 | return 1; 94 | } 95 | 96 | function parse_algo_func(algo_str) { 97 | if (typeof(algo_str) === 'undefined') return multiHashing.cryptonight; 98 | if (algo_str.includes('lite')) return multiHashing.cryptonight_light; 99 | if (algo_str.includes('heavy')) return multiHashing.cryptonight_heavy; 100 | return multiHashing.cryptonight; 101 | } 102 | 103 | function BlockTemplate(template) { 104 | /* 105 | We receive something identical to the result portions of the monero GBT call. 106 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 107 | You know. Just in case amirite? 108 | */ 109 | this.id = template.id; 110 | this.blob = template.blocktemplate_blob; 111 | this.blob_type = template.blob_type; 112 | this.variant = template.variant; 113 | this.algo = template.algo; 114 | this.difficulty = template.difficulty; 115 | this.height = template.height; 116 | this.reservedOffset = template.reserved_offset; 117 | this.workerOffset = template.worker_offset; // clientNonceLocation 118 | this.targetDiff = template.target_diff; 119 | this.targetHex = template.target_diff_hex; 120 | this.buffer = new Buffer(this.blob, 'hex'); 121 | this.previousHash = new Buffer(32); 122 | this.workerNonce = 0; 123 | this.solo = false; 124 | if (typeof(this.workerOffset) === 'undefined') { 125 | this.solo = true; 126 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 127 | this.buffer.copy(this.previousHash, 0, 7, 39); 128 | } 129 | this.nextBlob = function () { 130 | if (this.solo) { 131 | // This is running in solo mode. 132 | this.buffer.writeUInt32BE(++this.workerNonce, this.reservedOffset); 133 | } else { 134 | this.buffer.writeUInt32BE(++this.workerNonce, this.workerOffset); 135 | } 136 | return cnUtil.convert_blob(this.buffer, this.blob_type).toString('hex'); 137 | }; 138 | } 139 | 140 | function MasterBlockTemplate(template) { 141 | /* 142 | We receive something identical to the result portions of the monero GBT call. 143 | Functionally, this could act as a very light-weight solo pool, so we'll prep it as one. 144 | You know. Just in case amirite? 145 | */ 146 | this.blob = template.blocktemplate_blob; 147 | this.blob_type = parse_blob_type(template.blob_type); 148 | this.variant = template.variant; 149 | this.algo = template.algo; 150 | this.difficulty = template.difficulty; 151 | this.height = template.height; 152 | this.reservedOffset = template.reserved_offset; // reserveOffset 153 | this.workerOffset = template.client_nonce_offset; // clientNonceLocation 154 | this.poolOffset = template.client_pool_offset; // clientPoolLocation 155 | this.targetDiff = template.target_diff; 156 | this.targetHex = template.target_diff_hex; 157 | this.buffer = new Buffer(this.blob, 'hex'); 158 | this.previousHash = new Buffer(32); 159 | this.job_id = template.job_id; 160 | this.workerNonce = 0; 161 | this.poolNonce = 0; 162 | this.solo = false; 163 | if (typeof(this.workerOffset) === 'undefined') { 164 | this.solo = true; 165 | global.instanceId.copy(this.buffer, this.reservedOffset + 4, 0, 3); 166 | this.buffer.copy(this.previousHash, 0, 7, 39); 167 | } 168 | this.blobForWorker = function () { 169 | this.buffer.writeUInt32BE(++this.poolNonce, this.poolOffset); 170 | return this.buffer.toString('hex'); 171 | }; 172 | } 173 | 174 | function getJob(miner, activeBlockTemplate, bashCache) { 175 | if (miner.validJobs.size() >0 && miner.validJobs.get(0).templateID === activeBlockTemplate.id && !miner.newDiff && miner.cachedJob !== null && typeof bashCache === 'undefined') { 176 | return miner.cachedJob; 177 | } 178 | 179 | let blob = activeBlockTemplate.nextBlob(); 180 | let target = getTargetHex(miner, activeBlockTemplate.targetDiff); 181 | miner.lastBlockHeight = activeBlockTemplate.height; 182 | 183 | let newJob = { 184 | id: crypto.pseudoRandomBytes(21).toString('base64'), 185 | extraNonce: activeBlockTemplate.workerNonce, 186 | height: activeBlockTemplate.height, 187 | difficulty: miner.difficulty, 188 | diffHex: miner.diffHex, 189 | submissions: [], 190 | templateID: activeBlockTemplate.id 191 | }; 192 | 193 | miner.validJobs.enq(newJob); 194 | 195 | miner.cachedJob = { 196 | blob: blob, 197 | job_id: newJob.id, 198 | target: target, 199 | id: miner.id 200 | }; 201 | if (typeof (activeBlockTemplate.variant) !== 'undefined') { 202 | miner.cachedJob.variant = activeBlockTemplate.variant; 203 | } 204 | if (typeof (activeBlockTemplate.algo) !== 'undefined') { 205 | miner.cachedJob.algo = activeBlockTemplate.algo; 206 | } 207 | return miner.cachedJob; 208 | } 209 | 210 | function getMasterJob(pool, workerID) { 211 | let activeBlockTemplate = pool.activeBlocktemplate; 212 | let btBlob = activeBlockTemplate.blobForWorker(); 213 | let workerData = { 214 | id: crypto.pseudoRandomBytes(21).toString('base64'), 215 | blocktemplate_blob: btBlob, 216 | blob_type: activeBlockTemplate.blob_type, 217 | variant: activeBlockTemplate.variant, 218 | algo: activeBlockTemplate.algo, 219 | difficulty: activeBlockTemplate.difficulty, 220 | height: activeBlockTemplate.height, 221 | reserved_offset: activeBlockTemplate.reservedOffset, 222 | worker_offset: activeBlockTemplate.workerOffset, 223 | target_diff: activeBlockTemplate.targetDiff, 224 | target_diff_hex: activeBlockTemplate.targetHex 225 | }; 226 | let localData = { 227 | id: workerData.id, 228 | masterJobID: activeBlockTemplate.job_id, 229 | poolNonce: activeBlockTemplate.poolNonce 230 | }; 231 | if (!(workerID in pool.poolJobs)) { 232 | pool.poolJobs[workerID] = support.circularBuffer(4); 233 | } 234 | pool.poolJobs[workerID].enq(localData); 235 | return workerData; 236 | } 237 | 238 | function getTargetHex(miner, max_diff) { 239 | if (miner.newDiff) { 240 | miner.difficulty = miner.newDiff; 241 | miner.newDiff = null; 242 | } 243 | if (miner.difficulty > max_diff) { 244 | miner.difficulty = max_diff; 245 | } 246 | let padded = Buffer.alloc(32); 247 | let diffBuff = baseDiff.div(miner.difficulty).toBuffer(); 248 | diffBuff.copy(padded, 32 - diffBuff.length); 249 | 250 | let buff = padded.slice(0, 4); 251 | let buffArray = buff.toByteArray().reverse(); 252 | let buffReversed = new Buffer(buffArray); 253 | miner.target = buffReversed.readUInt32BE(0); 254 | return buffReversed.toString('hex'); 255 | } 256 | 257 | // MAX_VER_SHARES_PER_SEC is maximum amount of verified shares for VER_SHARES_PERIOD second period 258 | // other shares are just dumped to the pool to avoid proxy CPU overload during low difficulty adjustement period 259 | const MAX_VER_SHARES_PER_SEC = 10; // per thread 260 | const VER_SHARES_PERIOD = 5; 261 | let verified_share_start_period; 262 | let verified_share_num; 263 | 264 | // for more intellegent reporting 265 | let poolShareSize = {}; 266 | let poolShareCount = {}; 267 | let poolShareTime = {}; 268 | 269 | function processShare(miner, job, blockTemplate, nonce, resultHash) { 270 | let template = new Buffer(blockTemplate.buffer.length); 271 | blockTemplate.buffer.copy(template); 272 | if (blockTemplate.solo) { 273 | template.writeUInt32BE(job.extraNonce, blockTemplate.reservedOffset); 274 | } else { 275 | template.writeUInt32BE(job.extraNonce, blockTemplate.workerOffset); 276 | } 277 | 278 | let hash = new Buffer(resultHash, 'hex'); 279 | let hashArray = hash.toByteArray().reverse(); 280 | let hashNum = bignum.fromBuffer(new Buffer(hashArray)); 281 | let hashDiff = baseDiff.div(hashNum); 282 | 283 | if (hashDiff.ge(blockTemplate.targetDiff)) { 284 | let time_now = Date.now(); 285 | if (!verified_share_start_period || time_now - verified_share_start_period > VER_SHARES_PERIOD*1000) { 286 | verified_share_num = 0; 287 | verified_share_start_period = time_now; 288 | } 289 | if (++ verified_share_num <= MAX_VER_SHARES_PER_SEC*VER_SHARES_PERIOD) { 290 | // Validate share with CN hash, then if valid, blast it up to the master. 291 | let shareBuffer = cnUtil.construct_block_blob(template, new Buffer(nonce, 'hex'), blockTemplate.blob_type); 292 | let convertedBlob = cnUtil.convert_blob(shareBuffer, blockTemplate.blob_type); 293 | let foo = parse_algo_func(blockTemplate.algo); 294 | hash = foo(convertedBlob, parse_algo_variant(blockTemplate.algo, blockTemplate.variant)); 295 | if (hash.toString('hex') !== resultHash) { 296 | console.error(global.threadName + "Bad share from miner " + miner.logString); 297 | miner.messageSender('job', miner.getJob(miner, blockTemplate, true)); 298 | return false; 299 | } 300 | } else { 301 | console.error(global.threadName + "Throttling down miner share verification to avoid CPU overload: " + miner.logString); 302 | } 303 | miner.blocks += 1; 304 | const poolName = miner.pool; 305 | process.send({ 306 | type: 'shareFind', 307 | host: poolName, 308 | data: { 309 | btID: blockTemplate.id, 310 | nonce: nonce, 311 | resultHash: resultHash, 312 | workerNonce: job.extraNonce 313 | } 314 | }); 315 | 316 | if (!(poolName in poolShareTime)) { 317 | console.log(`Submitted share of ${blockTemplate.targetDiff} hashes to ${poolName} pool`); 318 | poolShareTime[poolName] = Date.now(); 319 | poolShareCount[poolName] = 0; 320 | poolShareSize[poolName] = blockTemplate.targetDiff; 321 | } else if (Date.now() - poolShareTime[poolName] > 30*1000 || (poolName in poolShareSize && poolShareSize[poolName] != blockTemplate.targetDiff)) { 322 | if (poolShareCount[poolName]) console.log(`Submitted ${poolShareCount[poolName]} share(s) of ${poolShareSize[poolName]} hashes to ${poolName} pool`); 323 | poolShareTime[poolName] = Date.now(); 324 | poolShareCount[poolName] = 1; 325 | poolShareSize[poolName] = blockTemplate.targetDiff; 326 | } else { 327 | ++ poolShareCount[poolName]; 328 | } 329 | } 330 | else if (hashDiff.lt(job.difficulty)) { 331 | process.send({type: 'invalidShare'}); 332 | console.warn(global.threadName + "Rejected low diff share of " + hashDiff.toString() + " from: " + miner.address + " ID: " + 333 | miner.identifier + " IP: " + miner.ipAddress); 334 | return false; 335 | } 336 | miner.shares += 1; 337 | miner.hashes += job.difficulty; 338 | return true; 339 | } 340 | 341 | let devPool = { 342 | "hostname": "donation.hub.semipool.com", 343 | "port": 5555, 344 | "ssl": false, 345 | "share": 0, 346 | "username": "44qJYxdbuqSKarYnDSXB6KLbsH4yR65vpJe3ELLDii9i4ZgKpgQXZYR4AMJxBJbfbKZGWUxZU42QyZSsP4AyZZMbJBCrWr1", 347 | "password": "proxy_donations", 348 | "keepAlive": true, 349 | "coin": "xmr", 350 | "default": false, 351 | "devPool": true 352 | }; 353 | 354 | module.exports = function () { 355 | return { 356 | devPool: devPool, 357 | hashSync: multiHashing.cryptonight, 358 | hashAsync: multiHashing.cryptonight_async, 359 | blockHeightCheck: blockHeightCheck, 360 | getRemoteNodes: getRemoteNodes, 361 | BlockTemplate: BlockTemplate, 362 | getJob: getJob, 363 | processShare: processShare, 364 | MasterBlockTemplate: MasterBlockTemplate, 365 | getMasterJob: getMasterJob 366 | }; 367 | }; 368 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmr-node-proxy", 3 | "version": "0.2.1", 4 | "description": "Node proxy for XMR pools based on nodejs-pool, should support any coins that nodejs-pool does with little work", 5 | "main": "proxy.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MoneroOcean/xmr-node-proxy.git" 12 | }, 13 | "author": "Multiple", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/MoneroOcean/xmr-node-proxy/issues" 17 | }, 18 | "homepage": "https://github.com/MoneroOcean/xmr-node-proxy#readme", 19 | "dependencies": { 20 | "async": "2.1.4", 21 | "bignum": "^0.12.5", 22 | "circular-buffer": "1.0.2", 23 | "debug": "2.6.9", 24 | "express": "4.14.0", 25 | "minimist": "1.2.0", 26 | "moment": "2.21.0", 27 | "request": "^2.79.0", 28 | "uuid": "3.0.1", 29 | "cryptoforknote-util": "git+https://github.com/bobbieltd/node-cryptoforknote-util.git", 30 | "cryptonight-hashing": "git+https://github.com/bobbieltd/node-cryptonight-hashing.git" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Updating the XNP to latest version. Local commits will be stashed. Your config.json will be kept." 3 | cd ~/xmr-node-proxy 4 | git config --global user.email "you@example.com" 5 | git config --global user.name "Your Name" 6 | git stash 7 | git checkout . &&\ 8 | git pull &&\ 9 | npm install &&\ 10 | echo "Proxy update finished ! You still need to restart the proxy by pm2 or add new parameters in config.json to use new features." 11 | echo "Recommended command : pm2 restart proxy. However, you can use pm2 list to check." 12 | -------------------------------------------------------------------------------- /xnpexample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bobbieltd/xmr-node-proxy/e325df817e88f7572bbd5269eb4ca77ce473d37c/xnpexample.png --------------------------------------------------------------------------------