From cea68b771f1a5d112d5cbf02445fa8984bfb463a Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Mon, 13 Jan 2014 17:45:10 -0700 Subject: [PATCH] Updated structure --- README.md | 12 +- example/coins/dogecoin_example.json | 4 +- example/shareManager.js | 40 -- index.js | 264 ++------ {libs => lib}/blockTemplate.js | 226 +++---- {libs => lib}/daemon.js | 240 +++---- {libs => lib}/jobManager.js | 414 ++++++------ {libs => lib}/merkleTree.js | 132 ++-- lib/pool.js | 226 +++++++ {libs => lib}/stratum.js | 577 ++++++++-------- {libs => lib}/transactions.js | 218 +++--- {libs => lib}/util.js | 618 +++++++++--------- node_modules/bignum/package.json | 2 +- .../binpack/node_modules/bindings/bindings.js | 2 +- .../node_modules/bindings/bindings.js | 2 +- .../node_modules/bindings/bindings.js | 2 +- .../node_modules/bindings/bindings.js | 2 +- package.json | 39 ++ 18 files changed, 1521 insertions(+), 1499 deletions(-) delete mode 100644 example/shareManager.js rename {libs => lib}/blockTemplate.js (93%) rename {libs => lib}/daemon.js (92%) rename {libs => lib}/jobManager.js (90%) rename {libs => lib}/merkleTree.js (95%) create mode 100644 lib/pool.js rename {libs => lib}/stratum.js (83%) rename {libs => lib}/transactions.js (96%) rename {libs => lib}/util.js (96%) create mode 100644 package.json diff --git a/README.md b/README.md index 66cf3a1..e0faebf 100644 --- a/README.md +++ b/README.md @@ -32,16 +32,6 @@ Requirements ------------ * node v0.10+ * coin daemon -* PostgreSQL -* npm dependencies - * [scrypt256-hash](https://github.com/zone117x/node-scrypt256-hash) - * [scrypt-jane-hash](https://github.com/zone117x/node-scrypt-jane-hash) - * [quark-hash](https://github.com/zone117x/node-quark-hash) - * [binpack](https://github.com/russellmcc/node-binpack) - * [bignum](https://github.com/justmoon/node-bignum) - * [buffertools] (https://github.com/bnoordhuis/node-buffertools) - * [base58-native](https://github.com/gasteve/node-base58) - * [async](https://github.com/caolan/async) Installation @@ -98,7 +88,7 @@ Installation * To start the poolserver run: ```bash - node init.js + node index.js ``` diff --git a/example/coins/dogecoin_example.json b/example/coins/dogecoin_example.json index 14ff3d8..8ef6b21 100644 --- a/example/coins/dogecoin_example.json +++ b/example/coins/dogecoin_example.json @@ -6,13 +6,11 @@ "address": "n3s8iDk1onxyY2nuC1k4HoRQFGJ7BhjFcq", "stratumPort": 3334, "difficulty": 8, - "blockRefreshInterval": 1, - "merkleRefreshInterval": 60, + "blockRefreshInterval": 5, "daemon": { "host": "localhost", "port": 19334, "user": "testnet", "password": "testnet1" - } } diff --git a/example/shareManager.js b/example/shareManager.js deleted file mode 100644 index 85fb296..0000000 --- a/example/shareManager.js +++ /dev/null @@ -1,40 +0,0 @@ -var events = require('events'); - -/** - * This ShareManager Events table: will emit the following events: - * - * LISTENS on: - * - pool('share') -**/ - -var ShareManager = exports.ShareManager = function(pool) { - pool.on('share', function(isValid, data) { - if (isValid) { - handleValidShare( - data.client, - data.blockHeaderHex, - data.jobId, - data.extraNonce1, - data.extraNonce2, - data.nTime, - data.nonce); - } else { - handleInvalidShare( - data.client, - data.error[0], - data.error[1]); - } - }); - - function handleValidShare(client, headerHex, jobId, extraNonce1, extraNonce2, nTime, nonce) { - console.log("A new Valid share from "+client.workerName+" has arrived! - "+headerHex); - } - - function handleInvalidShare(client, errorCode, errorDescription) { - console.log("Invalid share form "+client.workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription); - } -}; - -ShareManager.prototype.__proto__ = events.EventEmitter.prototype; - - diff --git a/index.js b/index.js index 4b13708..1554a33 100644 --- a/index.js +++ b/index.js @@ -1,233 +1,67 @@ -var net = require('net'); -var events = require('events'); -var fs = require('fs'); -var async = require('async'); -var daemon = require('./libs/daemon.js'); -var stratum = require('./libs/stratum.js'); -var jobManager = require('./libs/jobManager.js'); -var util = require('./libs/util.js'); +var net = require('net'); +var fs = require('fs'); +var path = require('path'); +var pool = require('lib/pool.js'); -/** - * Main pool object. It emits the following events: - * - 'started'() - when the pool is effectively started. - * - 'share'(isValid, dataObj) - In case it's valid the dataObj variable will contain (TODO) and in case it's invalid (TODO) - */ -var pool = module.exports = function pool(coin, authorizeFn){ + + +var index = module.exports = function index(options){ var _this = this; - var publicKeyBuffer; + this.pools = []; - (function Init(){ - SetupJobManager(); - SetupDaemonInterface(); - SetupShareManager(); - })(); + var emitLog = function(text){ + _this.emit('log', text); + }; - - function SetupJobManager(){ - _this.jobManager = new jobManager({ - algorithm : coin.options.algorithm, - address : coin.options.address - }); - _this.jobManager.on('newBlock', function(blockTemplate){ - if ( typeof(_this.stratumServer ) === 'undefined') { - console.warn("Stratum server still not started! cannot broadcast block!"); - } else { - _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); - } - - }).on('blockFound', function(blockHex, headerHex, third){ - if (coin.options.hasSubmitMethod) { - _this.daemon.cmd('submitblock', - [blockHex], - function(error, result){ - console.log(JSON.stringify(error)); - console.log(JSON.stringify(result)); - console.log("submitblock", JSON.stringify(error), JSON.stringify(result)); - } - ); - } else { - _this.daemon.cmd('getblocktemplate', - [{'mode': 'submit', 'data': blockHex}], - function(error, result){ - console.log(JSON.stringify(error)); - console.log(JSON.stringify(result)); - console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result)); - } - ); - } - }); + if (options.blockNotifyListener.enabled){ + SetupBlockListener(); } - function SetupDaemonInterface(){ - console.log('Connecting to daemon for ' + coin.options.name); - _this.daemon = new daemon.interface(coin.options.daemon); - _this.daemon.on('online', function(){ - async.parallel({ - addressInfo: function(callback){ - _this.daemon.cmd('validateaddress', - [coin.options.address], - function(error, result){ - if (error){ - console.log('validateaddress rpc error for ' + coin.options.name); - callback(error); - } else if (!result.isvalid) { - console.log('address is not valid for ' + coin.options.name); - callback("address-not-valid"); - } else { - console.log("LOALASD"+JSON.stringify(result)) - callback(error, result); - } + function SetupBlockListener(){ + console.log("Block listener is enabled, starting server on port " + config.blockNotifyListener.port); + var blockNotifyServer = net.createServer(function(c) { + emitLog('Block listener has incoming connection'); + var data = ''; + c.on('data', function(d){ + emitLog('Block listener received blocknotify data'); + data += d; + if (data.slice(-1) === '\n'){ + c.end(); + } + }); + c.on('end', function() { + + emitLog('Block listener connection ended'); + + var message = JSON.parse(data); + if (message.password === config.blockNotifyListener.password){ + + for (var i = 0; i < this.pools.length; i++){ + if (this.pools[i].options.symbol === message.coin){ + this.pools[i].processBlockNotify(message.blockHash) + return; } - ); - }, - submitMethod: function(callback){ - _this.daemon.cmd('submitblock', - [], - function(error, result){ - if (error && error.message === 'Method not found') - callback(null, false); - else - callback(null, true); - } - ); + } + emitLog('Block listener could not find pool to notify'); } - }, function(err, results){ - if (err) return; - - console.log('Connected to daemon for ' + coin.options.name); - coin.options.hasSubmitMethod = results.submitMethod; - - publicKeyBuffer = coin.options.reward === 'POW' ? - util.script_to_address(results.addressInfo.address) : - util.script_to_pubkey(results.addressInfo.pubkey); - - StartStratumServer(); - SetupBlockPolling(); + else + emitLog('Block listener received notification with incorrect password'); }); - - }).on('startFailed', function(){ - console.log('Failed to start daemon for ' + coin.name); + }); + blockNotifyServer.listen(options.blockNotifyListener.port, function() { + emitLog('Block notify listener server started on port ' + options.blockNotifyListener.port) }); } - function StartStratumServer(){ - console.log('Stratum server starting on port ' + coin.options.stratumPort + ' for ' + coin.options.name); - _this.stratumServer = new stratum.Server({ - port : coin.options.stratumPort, - authorizeFn : authorizeFn, - }); - _this.stratumServer.on('started', function(){ - console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name); - }).on('client.connected', function(client){ - client.on('subscription', function(params, resultCallback){ - - var extraNonce = _this.jobManager.extraNonceCounter.next(); - var extraNonce2Size = _this.jobManager.extraNonce2Size; - resultCallback(null, - extraNonce, - extraNonce2Size - ); - var clientThis = this; - - //if (clientThis.authorized) { - if (typeof(_this.jobManager.currentJob) !== 'undefined') { - clientThis.sendMiningJob(_this.jobManager.currentJob.getJobParams()); - } - - //} - - }).on('submit', function(params, resultCallback){ - var result =_this.jobManager.processShare( - params.jobId, - client.difficulty, - client.extraNonce1, - params.extraNonce2, - params.nTime, - params.nonce - ); - if (result.error){ - resultCallback(result.error); - _this.emit('share', false, { - client : client, - error : result.error - }); - } else { - resultCallback(null, true); - _this.emit('share', true, { - client : client, - blockHeaderHex : result.headerHEX, - workerName : params.name, - jobId : params.jobId, - extraNonce2 : params.extraNonce2, - nTime : params.nTime, - nonce : params.nonce - }); - } - - }); - }); - } - - - function SetupShareManager(){ - this.shareManager = undefined; // just for us to know that the variable should be this one. - } - - - function SetupBlockPolling(){ - - if (coin.options.blockRefreshInterval === 0){ - console.log('Block template polling has been disabled for ' + coin.options.name); - return; - } - - var pollingInterval = coin.options.blockRefreshInterval * 1000; - var pollTimeout; - var setPoll; - - setInterval(function () { - GetBlockTemplate(function(error, result) { - if (error) - console.error("Block polling error getting block template for " + coin.options.name); - - }); - - }, pollingInterval); - console.log('Block polling setup for every ' + pollingInterval + ' milliseconds for ' + coin.options.name); - } - - - function GetBlockTemplate(callback){ - _this.daemon.cmd('getblocktemplate', - [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], - function(error, result){ - if (error) { - callback(error); - } else { - _this.jobManager.processTemplate(result, publicKeyBuffer); - callback(null, result); - } - } - ); - } - - - /** - * This method is being called from the blockNotify so that when a new block is discovered by the daemon - * We can inform our miners about the newly found block - **/ - this.processBlockNotify = function(blockHash){ - if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ - GetBlockTemplate(function(error, result){ - if (error) - console.error('Block notify error getting block template for ' + coin.options.name); - }) - } - } + this.createPool = function(poolOptions, authorizeFn){ + var newPool = new pool(poolOptions, authorizeFn); + this.pools.push(newPool); + return newPool; + }; }; -pool.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file +index.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/libs/blockTemplate.js b/lib/blockTemplate.js similarity index 93% rename from libs/blockTemplate.js rename to lib/blockTemplate.js index 396b1d2..f776dd0 100644 --- a/libs/blockTemplate.js +++ b/lib/blockTemplate.js @@ -1,114 +1,114 @@ -var binpack = require('binpack'); -var merkleTree = require('./merkleTree.js'); -var transactions = require('./transactions.js'); -var util = require('./util.js'); - - -/** - * The BlockTemplate class holds a single job. - * and provides serveral methods to validate and submit it to the daemon coin -**/ -var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){ - - //private members - - var submits = []; - - function getMerkleHashes(steps){ - return steps.map(function(step){ - return step.toString('hex'); - }); - } - - function getTransactionBuffers(txs){ - var txHashes = txs.map(function(tx){ - return util.uint256BufferFromHash(tx.hash); - }); - return [null].concat(txHashes); - } - - - //public members - - this.rpcData = rpcData; - this.jobId = null; - this.target = util.bignumFromBits(rpcData.bits); - this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex'); - this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){ - return new Buffer(tx.data, 'hex'); - })); - this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions)); - this.merkleBranch = getMerkleHashes(this.merkleTree.steps); - this.generationTransaction = new transactions.Generation( - rpcData, - publicKey, - extraNoncePlaceholder - ); - - this.setJobId = function (jobId) { - this.jobId = jobId; - } - - this.serializeCoinbase = function(extraNonce1, extraNonce2){ - return Buffer.concat([ - this.generationTransaction.coinbase[0], - extraNonce1, - extraNonce2, - this.generationTransaction.coinbase[1] - ]); - }; - - this.serializeHeader = function(merkleRoot, nTime, nonce){ - - var header = new Buffer(80); - var position = 0; - header.write(nonce, position, 4, 'hex'); - header.write(rpcData.bits, position += 4, 4, 'hex'); - header.write(nTime, position += 4, 4, 'hex'); - header.write(merkleRoot, position += 4, 32, 'hex'); - header.write(rpcData.previousblockhash, position += 32, 32, 'hex'); - header.writeUInt32BE(rpcData.version, position + 32); - var header = util.reverseBuffer(header); - - return header; - }; - - this.serializeBlock = function(header, coinbase){ - return Buffer.concat([ - header, - util.varIntBuffer(this.rpcData.transactions.length + 1), - coinbase, - this.transactionData - ]); - }; - - this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){ - var submission = extraNonce1 + extraNonce2 + nTime + nonce; - if (submits.indexOf(submission) === -1){ - submits.push(submission); - return true; - } - return false; - }; - - this.getJobParams = function(){ - if (!this.jobParams){ - this.jobParams = [ - this.jobId, - this.prevHashReversed, - this.generationTransaction.coinbase[0].toString('hex'), - this.generationTransaction.coinbase[1].toString('hex'), - this.merkleBranch, - binpack.packInt32(this.rpcData.version, 'big').toString('hex'), - this.rpcData.bits, - binpack.packUInt32(this.rpcData.curtime, 'big').toString('hex'), - true - ]; - } - return this.jobParams; - } - - //this.jobParams = this.getJobParams(); - //console.log(JSON.stringify(this.jobParams, null, ' ').replace(/\n/g ,'')); - +var binpack = require('binpack'); +var merkleTree = require('./merkleTree.js'); +var transactions = require('./transactions.js'); +var util = require('./util.js'); + + +/** + * The BlockTemplate class holds a single job. + * and provides serveral methods to validate and submit it to the daemon coin +**/ +var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){ + + //private members + + var submits = []; + + function getMerkleHashes(steps){ + return steps.map(function(step){ + return step.toString('hex'); + }); + } + + function getTransactionBuffers(txs){ + var txHashes = txs.map(function(tx){ + return util.uint256BufferFromHash(tx.hash); + }); + return [null].concat(txHashes); + } + + + //public members + + this.rpcData = rpcData; + this.jobId = null; + this.target = util.bignumFromBits(rpcData.bits); + this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex'); + this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){ + return new Buffer(tx.data, 'hex'); + })); + this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions)); + this.merkleBranch = getMerkleHashes(this.merkleTree.steps); + this.generationTransaction = new transactions.Generation( + rpcData, + publicKey, + extraNoncePlaceholder + ); + + this.setJobId = function (jobId) { + this.jobId = jobId; + } + + this.serializeCoinbase = function(extraNonce1, extraNonce2){ + return Buffer.concat([ + this.generationTransaction.coinbase[0], + extraNonce1, + extraNonce2, + this.generationTransaction.coinbase[1] + ]); + }; + + this.serializeHeader = function(merkleRoot, nTime, nonce){ + + var header = new Buffer(80); + var position = 0; + header.write(nonce, position, 4, 'hex'); + header.write(rpcData.bits, position += 4, 4, 'hex'); + header.write(nTime, position += 4, 4, 'hex'); + header.write(merkleRoot, position += 4, 32, 'hex'); + header.write(rpcData.previousblockhash, position += 32, 32, 'hex'); + header.writeUInt32BE(rpcData.version, position + 32); + var header = util.reverseBuffer(header); + + return header; + }; + + this.serializeBlock = function(header, coinbase){ + return Buffer.concat([ + header, + util.varIntBuffer(this.rpcData.transactions.length + 1), + coinbase, + this.transactionData + ]); + }; + + this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){ + var submission = extraNonce1 + extraNonce2 + nTime + nonce; + if (submits.indexOf(submission) === -1){ + submits.push(submission); + return true; + } + return false; + }; + + this.getJobParams = function(){ + if (!this.jobParams){ + this.jobParams = [ + this.jobId, + this.prevHashReversed, + this.generationTransaction.coinbase[0].toString('hex'), + this.generationTransaction.coinbase[1].toString('hex'), + this.merkleBranch, + binpack.packInt32(this.rpcData.version, 'big').toString('hex'), + this.rpcData.bits, + binpack.packUInt32(this.rpcData.curtime, 'big').toString('hex'), + true + ]; + } + return this.jobParams; + } + + //this.jobParams = this.getJobParams(); + //console.log(JSON.stringify(this.jobParams, null, ' ').replace(/\n/g ,'')); + } \ No newline at end of file diff --git a/libs/daemon.js b/lib/daemon.js similarity index 92% rename from libs/daemon.js rename to lib/daemon.js index 9f17e55..c98cb30 100644 --- a/libs/daemon.js +++ b/lib/daemon.js @@ -1,121 +1,121 @@ -var http = require('http'); -var cp = require('child_process'); -var events = require('events'); -var startFailedTimeout = 120; //seconds - -/** - * The daemon interface interacts with the coin daemon by using the rpc interface. - * in otder to make it work it needs, as constructor, an object containing - * - 'host' : hostname where the coin lives - * - 'port' : port where the coin accepts rpc connections - * - 'user' : username of the coin for the rpc interface - * - 'password': password for the rpc interface of the coin -**/ - -function DaemonInterface(options){ - - //private members - var _this = this; - this.options = options; - - (function init(){ - isOnline(function(online){ - if (online) - _this.emit('online'); - else if (options.startIfOffline){ - me.start(); - emitOnline(); - } - }); - })(); - - function emitOnline(){ - var startedTime = Date.now(); - var checkFunc = function(){ - isOnline(function(online){ - if (online) - _this.emit('online'); - else if (Date.now() - startedTime < startFailedTimeout * 1000) - setTimeout(checkFunc, 2000); - else - _this.emit('startFailed'); - }); - }; - checkFunc(); - } - - function isOnline(callback){ - cmd('getinfo', [], function(error, result){ - if (error) - callback(false); - else - callback(true); - }); - } - - function cmd(method, params, callback){ - - var requestJson = JSON.stringify({ - id: Date.now() + Math.floor(Math.random() * 10), - method: method, - params: params - }); - - if (method == 'submitblock') { - console.log("SUBMITBLOCK daemon"); - console.log(requestJson); - } - - var options = { - hostname: (typeof(_this.options.host) === 'undefined'?'localhost':_this.options.host), - port : _this.options.port, - method : 'POST', - auth : _this.options.user + ':' + _this.options.password, - headers : { - 'Content-Length': requestJson.length - } - }; - - var req = http.request(options, function(res) { - var data = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - data += chunk; - }); - res.on('end', function(){ - var dataJson = JSON.parse(data); - callback(dataJson.error, dataJson.result); - }); - }); - - req.on('error', function(e) { - if (e.code === 'ECONNREFUSED') - callback({type: 'offline', message: e.message}); - else - callback({type: 'request error', message: e.message}); - }); - - req.end(requestJson); - } - - - //public members - - this.isOnline = isOnline; - this.cmd = cmd; - this.start = function(){ - var cmdArgs = [ - '-rpcport=' + _this.options.port, - '-rpcuser=' + _this.options.user, - '-rpcpassword=' + _this.options.password, - '-blocknotify=' + _this.options.blocknotify - ]; - var child = cp.spawn(_this.options.bin, cmdArgs, { detached: true, stdio: [ 'ignore', 'ignore', 'ignore' ] }); - child.unref(); - console.log('started daemon'); - }; -} - -DaemonInterface.prototype.__proto__ = events.EventEmitter.prototype; - +var http = require('http'); +var cp = require('child_process'); +var events = require('events'); +var startFailedTimeout = 120; //seconds + +/** + * The daemon interface interacts with the coin daemon by using the rpc interface. + * in otder to make it work it needs, as constructor, an object containing + * - 'host' : hostname where the coin lives + * - 'port' : port where the coin accepts rpc connections + * - 'user' : username of the coin for the rpc interface + * - 'password': password for the rpc interface of the coin +**/ + +function DaemonInterface(options){ + + //private members + var _this = this; + this.options = options; + + (function init(){ + isOnline(function(online){ + if (online) + _this.emit('online'); + else if (options.startIfOffline){ + me.start(); + emitOnline(); + } + }); + })(); + + function emitOnline(){ + var startedTime = Date.now(); + var checkFunc = function(){ + isOnline(function(online){ + if (online) + _this.emit('online'); + else if (Date.now() - startedTime < startFailedTimeout * 1000) + setTimeout(checkFunc, 2000); + else + _this.emit('startFailed'); + }); + }; + checkFunc(); + } + + function isOnline(callback){ + cmd('getinfo', [], function(error, result){ + if (error) + callback(false); + else + callback(true); + }); + } + + function cmd(method, params, callback){ + + var requestJson = JSON.stringify({ + id: Date.now() + Math.floor(Math.random() * 10), + method: method, + params: params + }); + + if (method == 'submitblock') { + console.log("SUBMITBLOCK daemon"); + console.log(requestJson); + } + + var options = { + hostname: (typeof(_this.options.host) === 'undefined'?'localhost':_this.options.host), + port : _this.options.port, + method : 'POST', + auth : _this.options.user + ':' + _this.options.password, + headers : { + 'Content-Length': requestJson.length + } + }; + + var req = http.request(options, function(res) { + var data = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + data += chunk; + }); + res.on('end', function(){ + var dataJson = JSON.parse(data); + callback(dataJson.error, dataJson.result); + }); + }); + + req.on('error', function(e) { + if (e.code === 'ECONNREFUSED') + callback({type: 'offline', message: e.message}); + else + callback({type: 'request error', message: e.message}); + }); + + req.end(requestJson); + } + + + //public members + + this.isOnline = isOnline; + this.cmd = cmd; + this.start = function(){ + var cmdArgs = [ + '-rpcport=' + _this.options.port, + '-rpcuser=' + _this.options.user, + '-rpcpassword=' + _this.options.password, + '-blocknotify=' + _this.options.blocknotify + ]; + var child = cp.spawn(_this.options.bin, cmdArgs, { detached: true, stdio: [ 'ignore', 'ignore', 'ignore' ] }); + child.unref(); + console.log('started daemon'); + }; +} + +DaemonInterface.prototype.__proto__ = events.EventEmitter.prototype; + exports.interface = DaemonInterface; \ No newline at end of file diff --git a/libs/jobManager.js b/lib/jobManager.js similarity index 90% rename from libs/jobManager.js rename to lib/jobManager.js index 2868ac2..0eda2be 100644 --- a/libs/jobManager.js +++ b/lib/jobManager.js @@ -1,209 +1,207 @@ -var events = require('events'); - -var binpack = require('binpack'); -var bignum = require('bignum'); - -var scrypt = require('scrypt256-hash'); -var quark = require('quark-hash'); -var scryptJane = require('scrypt-jane-hash') - - -var util = require('./util.js'); -var blockTemplate = require('./blockTemplate.js'); - - - -//Unique extranonce per subscriber -var ExtraNonceCounter = function(){ - var instanceId = 31; - var counter = instanceId << 27; - var size = binpack.packUInt32(counter, 'big').length; - - this.next = function(){ - var extraNonce = binpack.packUInt32(counter++, 'big'); - return extraNonce.toString('hex'); - }; - this.size = function(){ - return size; - }; -}; - -//Unique job per new block template -var JobCounter = function(){ - var counter = 0; - - this.next = function(){ - counter++; - if (counter % 0xffff === 0) - counter = 1; - return this.cur(); - }; - - this.cur = function () { - return counter.toString(16); - }; -}; - -/** - * Emits: - * - 'newBlock'(blockTemplate) - when a new block (previously unknown to the JobManager) is being added - * - 'blockFound'(serializedBlock) - when a worker finds a block. -**/ -var JobManager = module.exports = function JobManager(options){ - - //private members - - var _this = this; - var jobCounter = new JobCounter(); - var jobs = {}; - - - /** - * It only checks if the blockTemplate is already in our jobs list. - * @returns true if it's a new block, false otherwise. - * used by onNewTemplate - **/ - function CheckNewIfNewBlock(prevBlockHash){ - - var newBlock = true; - for(var job in jobs){ - if (jobs[job].rpcData.previousblockhash === prevBlockHash) { - newBlock = false; - } - } - return newBlock; - } - - var diffDividend = (function(){ - switch(options.algorithm){ - case 'sha256': - return 0x00000000ffff0000000000000000000000000000000000000000000000000000; - case 'scrypt': - case 'scrypt-jane': - return 0x0000ffff00000000000000000000000000000000000000000000000000000000; - case 'quark': - return 0x000000ffff000000000000000000000000000000000000000000000000000000; - } - })(); - - var hashDigest = (function(){ - switch(options.algorithm){ - case 'sha256': - return function(){ - return util.doublesha.apply(this, arguments); - } - case 'scrypt': - return function(){ - return scrypt.digest.apply(this, arguments); - } - case 'scrypt-jane': - return function(){ - return scryptJane.digest.apply(this, arguments); - } - case 'quark': - return function(){ - return quark.digest.apply(this, arguments); - } - } - })(); - - /** - * Tries to estimate the resulting block hash - * This is only valid for scrypt apparently. - * @author vekexasia - **/ - function blockHashHex(headerBuffer) { - var result = new Buffer(80); - for (var i=0; i<20; i++) { - for (var j=0; j<4; j++) { - result[i*4+j] = headerBuffer[i*4+3-j]; - } - } - var shaed = util.reverseBuffer(util.doublesha(result)); - - - return shaed.toString('hex'); // return the expected block hash - - } - - //public members - - this.extraNonceCounter = new ExtraNonceCounter(); - this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex'); - this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size(); - - this.currentJob; - - this.processTemplate = function(rpcData, publicKey){ - if (CheckNewIfNewBlock(rpcData.previousblockhash)){ - var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder); - tmpBlockTemplate.setJobId(jobCounter.next()); - jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate; - this.currentJob = jobs[tmpBlockTemplate.jobId]; - _this.emit('newBlock', tmpBlockTemplate); - } - }; - - this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){ - - var submitTime = Date.now() / 1000 | 0; - - if (extraNonce2.length / 2 !== _this.extraNonce2Size) - return {error: [20, 'incorrect size of extranonce2', null]}; - - var job = jobs[jobId]; - if (!job) { - return {error: [21, 'job not found', null]}; - } - - if (nTime.length !== 8) { - return {error: [20, 'incorrect size of ntime']}; - } - - var nTimeInt = parseInt(nTime, 16); - if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) { - return {error: [20, 'ntime out of range', null]}; - } - - if (nonce.length !== 8) { - return {error: [20, 'incorrect size of nonce']}; - } - - if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) { - return {error: [22, 'duplicate share', null]}; - } - - - var extraNonce1Buffer = new Buffer(extraNonce1, 'hex'); - var extraNonce2Buffer = new Buffer(extraNonce2, 'hex'); - - var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer); - var coinbaseHash = util.doublesha(coinbaseBuffer); - - var merkleRoot = job.merkleTree.withFirst(coinbaseHash); - merkleRoot = util.reverseBuffer(merkleRoot).toString('hex'); - - var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce); - var headerHash = hashDigest(headerBuffer, nTimeInt); - var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32}); - - - - if (job.target.ge(headerBigNum)){ - var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); - console.log("EXPECTED BLOCK HASH: "+blockHashHex(headerBuffer)); // NOT WORKING :(? - _this.emit('blockFound', blockBuf.toString('hex')); - } else { - // If block is not found we want also to check the difficulty of the share. - // TODO: this seems to not be working properly - var targetUser = bignum(diffDividend / difficulty); - - if (headerBigNum.gt(targetUser)){ - return {error: [23, 'low difficulty share', null]}; - } - } - - return {result: true, headerHEX: headerBigNum.toString(16)}; - }; -}; +var events = require('events'); + +var binpack = require('binpack'); +var bignum = require('bignum'); + +var scrypt = require('scrypt256-hash'); +var quark = require('quark-hash'); +var scryptJane = require('scrypt-jane-hash') + + +var util = require('./util.js'); +var blockTemplate = require('./blockTemplate.js'); + + + +//Unique extranonce per subscriber +var ExtraNonceCounter = function(){ + var instanceId = 31; + var counter = instanceId << 27; + var size = binpack.packUInt32(counter, 'big').length; + + this.next = function(){ + var extraNonce = binpack.packUInt32(counter++, 'big'); + return extraNonce.toString('hex'); + }; + this.size = function(){ + return size; + }; +}; + +//Unique job per new block template +var JobCounter = function(){ + var counter = 0; + + this.next = function(){ + counter++; + if (counter % 0xffff === 0) + counter = 1; + return this.cur(); + }; + + this.cur = function () { + return counter.toString(16); + }; +}; + +/** + * Emits: + * - 'newBlock'(blockTemplate) - when a new block (previously unknown to the JobManager) is being added + * - 'blockFound'(serializedBlock) - when a worker finds a block. +**/ +var JobManager = module.exports = function JobManager(options){ + + //private members + + var _this = this; + var jobCounter = new JobCounter(); + var jobs = {}; + + + /** + * It only checks if the blockTemplate is already in our jobs list. + * @returns true if it's a new block, false otherwise. + * used by onNewTemplate + **/ + function CheckNewIfNewBlock(prevBlockHash){ + + var newBlock = true; + for(var job in jobs){ + if (jobs[job].rpcData.previousblockhash === prevBlockHash) { + newBlock = false; + } + } + return newBlock; + } + + var diffDividend = (function(){ + switch(options.algorithm){ + case 'sha256': + return 0x00000000ffff0000000000000000000000000000000000000000000000000000; + case 'scrypt': + case 'scrypt-jane': + return 0x0000ffff00000000000000000000000000000000000000000000000000000000; + case 'quark': + return 0x000000ffff000000000000000000000000000000000000000000000000000000; + } + })(); + + var hashDigest = (function(){ + switch(options.algorithm){ + case 'sha256': + return function(){ + return util.doublesha.apply(this, arguments); + } + case 'scrypt': + return function(){ + return scrypt.digest.apply(this, arguments); + } + case 'scrypt-jane': + return function(){ + return scryptJane.digest.apply(this, arguments); + } + case 'quark': + return function(){ + return quark.digest.apply(this, arguments); + } + } + })(); + + /** + * Tries to estimate the resulting block hash + * This is only valid for scrypt apparently. + * @author vekexasia + **/ + function blockHashHex(headerBuffer) { + var result = new Buffer(80); + for (var i=0; i<20; i++) { + for (var j=0; j<4; j++) { + result[i*4+j] = headerBuffer[i*4+3-j]; + } + } + var shaed = util.reverseBuffer(util.doublesha(result)); + + + return shaed.toString('hex'); // return the expected block hash + + } + + //public members + + this.extraNonceCounter = new ExtraNonceCounter(); + this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex'); + this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size(); + + this.currentJob; + + this.processTemplate = function(rpcData, publicKey){ + if (CheckNewIfNewBlock(rpcData.previousblockhash)){ + var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder); + tmpBlockTemplate.setJobId(jobCounter.next()); + jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate; + this.currentJob = jobs[tmpBlockTemplate.jobId]; + _this.emit('newBlock', tmpBlockTemplate); + } + }; + + this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){ + + var submitTime = Date.now() / 1000 | 0; + + if (extraNonce2.length / 2 !== _this.extraNonce2Size) + return {error: [20, 'incorrect size of extranonce2', null]}; + + var job = jobs[jobId]; + if (!job) { + return {error: [21, 'job not found', null]}; + } + + if (nTime.length !== 8) { + return {error: [20, 'incorrect size of ntime']}; + } + + var nTimeInt = parseInt(nTime, 16); + if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) { + return {error: [20, 'ntime out of range', null]}; + } + + if (nonce.length !== 8) { + return {error: [20, 'incorrect size of nonce']}; + } + + if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) { + return {error: [22, 'duplicate share', null]}; + } + + + var extraNonce1Buffer = new Buffer(extraNonce1, 'hex'); + var extraNonce2Buffer = new Buffer(extraNonce2, 'hex'); + + var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer); + var coinbaseHash = util.doublesha(coinbaseBuffer); + + var merkleRoot = job.merkleTree.withFirst(coinbaseHash); + merkleRoot = util.reverseBuffer(merkleRoot).toString('hex'); + + var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce); + var headerHash = hashDigest(headerBuffer, nTimeInt); + var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32}); + + + + if (job.target.ge(headerBigNum)){ + var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); + console.log("EXPECTED BLOCK HASH: "+blockHashHex(headerBuffer)); // NOT WORKING :(? + _this.emit('blockFound', blockBuf.toString('hex')); + } + + // TODO: this seems to not be working properly + var targetUser = bignum(diffDividend / difficulty); + if (headerBigNum.gt(targetUser)){ + return {error: [23, 'low difficulty share', null]}; + } + + return {result: true, headerHEX: headerBigNum.toString(16)}; + }; +}; JobManager.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/libs/merkleTree.js b/lib/merkleTree.js similarity index 95% rename from libs/merkleTree.js rename to lib/merkleTree.js index 90f9c29..95fb0c4 100644 --- a/libs/merkleTree.js +++ b/lib/merkleTree.js @@ -1,67 +1,67 @@ -/* - -Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py - - */ - -var util = require('./util.js'); - -var MerkleTree = module.exports = function MerkleTree(data){ - - function merkleJoin(h1, h2){ - var joined = Buffer.concat([h1, h2]); - var dhashed = util.doublesha(joined); - return dhashed; - } - - function calculateSteps(data){ - var L = data; - var steps = []; - var PreL = [null]; - var StartL = 2; - var Ll = L.length; - - if (Ll > 1){ - while (true){ - - if (Ll === 1) - break; - - steps.push(L[1]); - - if (Ll % 2) - L.push(L[L.length - 1]); - - var Ld = []; - var r = util.range(StartL, Ll, 2); - r.forEach(function(i){ - Ld.push(merkleJoin(L[i], L[i + 1])); - }); - L = PreL.concat(Ld); - Ll = L.length; - } - } - return steps; - } - - this.data = data; - this.steps = calculateSteps(data); - -} -MerkleTree.prototype = { - - hashSteps: function(){ - if (!this.stepsHash) - this.stepsHash = util.doublesha(Buffer.concat(this.steps)); - return this.stepsHash; - }, - withFirst: function(f){ - this.steps.forEach(function(s){ - f = util.doublesha(Buffer.concat([f, s])); - }); - return f; - }, - merkleRoot: function(){ - return this.withFirst(this.data[0]); - } +/* + +Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py + + */ + +var util = require('./util.js'); + +var MerkleTree = module.exports = function MerkleTree(data){ + + function merkleJoin(h1, h2){ + var joined = Buffer.concat([h1, h2]); + var dhashed = util.doublesha(joined); + return dhashed; + } + + function calculateSteps(data){ + var L = data; + var steps = []; + var PreL = [null]; + var StartL = 2; + var Ll = L.length; + + if (Ll > 1){ + while (true){ + + if (Ll === 1) + break; + + steps.push(L[1]); + + if (Ll % 2) + L.push(L[L.length - 1]); + + var Ld = []; + var r = util.range(StartL, Ll, 2); + r.forEach(function(i){ + Ld.push(merkleJoin(L[i], L[i + 1])); + }); + L = PreL.concat(Ld); + Ll = L.length; + } + } + return steps; + } + + this.data = data; + this.steps = calculateSteps(data); + +} +MerkleTree.prototype = { + + hashSteps: function(){ + if (!this.stepsHash) + this.stepsHash = util.doublesha(Buffer.concat(this.steps)); + return this.stepsHash; + }, + withFirst: function(f){ + this.steps.forEach(function(s){ + f = util.doublesha(Buffer.concat([f, s])); + }); + return f; + }, + merkleRoot: function(){ + return this.withFirst(this.data[0]); + } }; \ No newline at end of file diff --git a/lib/pool.js b/lib/pool.js new file mode 100644 index 0000000..4a91a3f --- /dev/null +++ b/lib/pool.js @@ -0,0 +1,226 @@ +var net = require('net'); +var events = require('events'); +var fs = require('fs'); +var async = require('async'); +var daemon = require('./daemon.js'); +var stratum = require('./stratum.js'); +var jobManager = require('./jobManager.js'); +var util = require('./util.js'); + +/** + * Main pool object. It emits the following events: + * - 'started'() - when the pool is effectively started. + * - 'share'(isValid, dataObj) - In case it's valid the dataObj variable will contain (TODO) and in case it's invalid (TODO) + */ +var pool = module.exports = function pool(options, authorizeFn){ + + this.options = options; + var _this = this; + var publicKeyBuffer; + + + var emitLog = function(text){ _this.emit('log', text) }; + + + (function Init(){ + SetupJobManager(); + SetupDaemonInterface(); + })(); + + + + function SetupJobManager(){ + _this.jobManager = new jobManager({ + algorithm : options.algorithm, + address : options.address + }); + _this.jobManager.on('newBlock', function(blockTemplate){ + if ( typeof(_this.stratumServer ) === 'undefined') { + console.warn("Stratum server still not started! cannot broadcast block!"); + } else { + _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); + } + }).on('blockFound', function(blockHex, headerHex, third){ + if (options.hasSubmitMethod) { + _this.daemon.cmd('submitblock', + [blockHex], + function(error, result){ + console.log(JSON.stringify(error)); + console.log(JSON.stringify(result)); + console.log("submitblock", JSON.stringify(error), JSON.stringify(result)); + } + ); + } else { + _this.daemon.cmd('getblocktemplate', + [{'mode': 'submit', 'data': blockHex}], + function(error, result){ + console.log(JSON.stringify(error)); + console.log(JSON.stringify(result)); + console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result)); + } + ); + } + }); + } + + + function SetupDaemonInterface(){ + emitLog('Connecting to daemon'); + _this.daemon = new daemon.interface(options.daemon); + _this.daemon.on('online', function(){ + async.parallel({ + addressInfo: function(callback){ + _this.daemon.cmd('validateaddress', + [options.address], + function(error, result){ + if (error){ + emitLog('validateaddress rpc error'); + callback(error); + } else if (!result.isvalid) { + emitLog('address is not valid'); + callback("address-not-valid"); + } else { + callback(error, result); + } + } + ); + }, + submitMethod: function(callback){ + _this.daemon.cmd('submitblock', + [], + function(error, result){ + if (error && error.message === 'Method not found') + callback(null, false); + else + callback(null, true); + } + ); + } + }, function(err, results){ + if (err) return; + + emitLog('Connected to daemon'); + options.hasSubmitMethod = results.submitMethod; + + publicKeyBuffer = options.reward === 'POW' ? + util.script_to_address(results.addressInfo.address) : + util.script_to_pubkey(results.addressInfo.pubkey); + + StartStratumServer(); + SetupBlockPolling(); + + }); + + }).on('startFailed', function(){ + emitLog('Failed to start daemon'); + }); + } + + + function StartStratumServer(){ + emitLog('Stratum server starting on port ' + options.stratumPort); + _this.stratumServer = new stratum.Server({ + port: options.stratumPort, + authorizeFn: authorizeFn + }); + _this.stratumServer.on('started', function(){ + emitLog('Stratum server started on port ' + options.stratumPort); + }).on('client.connected', function(client){ + client.on('subscription', function(params, resultCallback){ + + var extraNonce = _this.jobManager.extraNonceCounter.next(); + var extraNonce2Size = _this.jobManager.extraNonce2Size; + resultCallback(null, + extraNonce, + extraNonce2Size + ); + + this.sendAndSetDifficultyIfNew(options.difficulty); + this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); + + + }).on('submit', function(params, resultCallback){ + var result =_this.jobManager.processShare( + params.jobId, + client.difficulty, + client.extraNonce1, + params.extraNonce2, + params.nTime, + params.nonce + ); + if (result.error){ + resultCallback(result.error); + _this.emit('share', false, { + client : client, + error : result.error + }); + } else { + resultCallback(null, true); + _this.emit('share', true, { + client : client, + blockHeaderHex : result.headerHEX, + workerName : params.name, + jobId : params.jobId, + extraNonce2 : params.extraNonce2, + nTime : params.nTime, + nonce : params.nonce + }); + } + + }); + }); + } + + function SetupBlockPolling(){ + + if (options.blockRefreshInterval === 0){ + emitLog('Block template polling has been disabled'); + return; + } + + var pollingInterval = options.blockRefreshInterval * 1000; + var pollTimeout; + var setPoll; + + setInterval(function () { + GetBlockTemplate(function(error, result) { + if (error) + console.error("Block polling error getting block template for " + options.name); + + }); + + }, pollingInterval); + emitLog('Block polling setup for every ' + pollingInterval + ' milliseconds'); + } + + + function GetBlockTemplate(callback){ + _this.daemon.cmd('getblocktemplate', + [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], + function(error, result){ + if (error) { + callback(error); + } else { + _this.jobManager.processTemplate(result, publicKeyBuffer); + callback(null, result); + } + } + ); + } + + + /** + * This method is being called from the blockNotify so that when a new block is discovered by the daemon + * We can inform our miners about the newly found block + **/ + this.processBlockNotify = function(blockHash){ + if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ + GetBlockTemplate(function(error, result){ + if (error) + console.error('Block notify error getting block template for ' + options.name); + }) + } + } + +}; +pool.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/libs/stratum.js b/lib/stratum.js similarity index 83% rename from libs/stratum.js rename to lib/stratum.js index cda177c..98076e7 100644 --- a/libs/stratum.js +++ b/lib/stratum.js @@ -1,301 +1,278 @@ -var net = require('net'); -var events = require('events'); - -var binpack = require('binpack'); - -var util = require('./util.js'); - - -var SubscriptionCounter = function(){ - var count = 0; - var padding = 'deadbeefcafebabe'; - return { - next: function(){ - count++; - if (Number.MAX_VALUE === count) count = 0; - return padding + binpack.packUInt64(count, 'big').toString('hex'); - } - }; -}; - - -/** - * Defining each client that connects to the stratum server. - * Emits: - * - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size)) - * - 'submit' FIX THIS. -**/ -var StratumClient = function(options){ - - //private members - - var _this = this; - - (function init(){ - setupSocket(); - })(); - - function handleMessage(message){ - switch(message.method){ - case 'mining.subscribe': - handleSubscribe(message); - break; - case 'mining.authorize': - handleAuthorize(message); - break; - case 'mining.submit': - handleSubmit(message); - break; - case 'mining.get_transactions': - sendJson({ - id : null, - result : [], - error : true - }); - break; - default: - console.dir('unknown stratum client message: ' + JSON.stringify(message)); - break; - } - } - - function handleSubscribe(message){ - if (! _this._authorized ) { - _this.requestedSubscriptionBeforeAuth = true; - } - _this.emit('subscription', - {}, - function(error, extraNonce1, extraNonce2Size){ - if (error){ - sendJson({ - id: message.id, - result: null, - error: error - }); - return; - } - _this.extraNonce1 = extraNonce1; - sendJson({ - id: message.id, - result: [ - ["mining.notify", options.subscriptionId], - extraNonce1, - extraNonce2Size - ], - error: null - }); - } - ); - } - - function handleAuthorize(message){ - _this.workerIP = options.socket.address().address; - _this.workerName = message.params[0]; - _this.workerPass = message.params[1]; - options.authorizeFn(_this.workerIP, _this.workerName, _this.workerPass, function(err, authorized, shouldCloseSocket, difficulty) { - _this.authorized = ( ! err && authorized ); - sendJson({ - id : message.id, - result : _this.authorized, - error : err - }); - - // If the authorizer wants us to close the socket lets do it. - if (typeof(shouldCloseSocket) === 'boolean' && shouldCloseSocket) { - options.socket.end(); - } - - // Send difficulty - if (typeof(difficulty) === 'function') { - difficulty(_this.workerName, function(err, diff) { - if (err) { - console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err)); - options.socket.end(); - } else { - _this.sendAndSetDifficultyIfNew(diff); - } - - }); - } else if (typeof(difficulty) === 'number') { - _this.sendAndSetDifficultyIfNew(difficulty); - } else { - process.exit("Difficulty from authorizeFn callback is neither a function or a number"); - } - - if (_this.requestedSubscriptionBeforeAuth === true) { - delete _this.requestedSubscriptionBeforeAuth; // we do not need this anymore. - //TODO: We should send a mining job here. For now we send it before the auth has taken place but..... - } - - }); - } - - function handleSubmit(message){ - if (!_this.authorized){ - sendJson({ - id : message.id, - result: null, - error : [24, "unauthorized worker", null] - }); - return; - } - if (!_this.extraNonce1){ - sendJson({ - id : message.id, - result: null, - error : [25, "not subscribed", null] - }); - return; - } - _this.emit('submit', - { - name : message.params[0], - jobId : message.params[1], - extraNonce2 : message.params[2], - nTime : message.params[3], - nonce : message.params[4] - }, - function(error, result){ - sendJson({ - id : message.id, - result : result, - error : error - }); - } - ); - } - - function sendJson(){ - var response = ''; - for (var i = 0; i < arguments.length; i++){ - response += JSON.stringify(arguments[i]) + '\n'; - } - options.socket.write(response); - } - - function setupSocket(){ - var socket = options.socket; - var dataBuffer = ''; - socket.setEncoding('utf8'); - socket.on('data', function(d){ - console.log('request: ' + d); - dataBuffer += d; - if (dataBuffer.slice(-1) === '\n'){ - var messages = dataBuffer.split('\n'); - messages.forEach(function(message){ - if (message.trim() === '') return; - var messageJson; - try{ - messageJson = JSON.parse(message); - } - catch(e){ - console.log('could not parse stratum client socket message: ' + message); - } - if (messageJson) { - handleMessage(messageJson); - } - }); - dataBuffer = ''; - } - }); - socket.on('end', function() { - _this.emit('socketDisconnect') - console.log('stratum client disconnected'); - }); - socket.on('error', function(){ - _this.emit('socketError'); - console.log('stratum client socket error'); - }); - } - - - //public members - - /** - * IF the given difficulty is valid and new it'll send it to the client. - * returns boolean - **/ - this.sendAndSetDifficultyIfNew = function(difficulty){ - if (typeof(difficulty) != 'number') { - console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']'); - return false; - } - - if (difficulty !== this.difficulty) { - this.difficulty = difficulty; - sendJson({ - id : null, - method: "mining.set_difficulty", - params: [difficulty]//[512], - }); - return true; - } else { - return false; - } - - }; - - this.sendMiningJob = function(jobParams){ - sendJson({ - id : null, - method: "mining.notify", - params: jobParams - }); - }; -}; -StratumClient.prototype.__proto__ = events.EventEmitter.prototype; - - - -/** - * The actual stratum server. - * It emits the following Events: - * - 'client.connected'(StratumClientInstance) - when a new miner connects - * - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore. - * - 'started' - when the server is up and running - **/ -var StratumServer = exports.Server = function StratumServer(options){ - - //private members - - var _this = this; - var socketServer; - var stratumClients = {}; - var subscriptionCounter = SubscriptionCounter(); - - (function init(){ - _socketServer = socketServer = net.createServer(function(c){ - var subscriptionId = subscriptionCounter.next(); - var client = new StratumClient( - { - subscriptionId : subscriptionId, - socket : c, - authorizeFn : options.authorizeFn - } - ); - stratumClients[subscriptionId] = client; - _this.emit('client.connected', client); - client.on('socketDisconnect', function() { - delete stratumClients[subscriptionId]; - _this.emit('client.disconnected', client); - }); - }); - _socketServer.listen(options.port, function(){ - _this.emit('started'); - }); - })(); - - - //public members - - this.broadcastMiningJobs = function(jobParams) { - for (var clientId in stratumClients) { - // if a client gets disconnected WHILE doing this loop a crash might happen. - // 'm not sure if that can ever happn but an if here doesn't hurt! - if (typeof(stratumClients[clientId]) !== 'undefined') { - stratumClients[clientId].sendMiningJob(jobParams); - } - } - }; -}; +var net = require('net'); +var events = require('events'); + +var binpack = require('binpack'); + +var util = require('./util.js'); + + +var SubscriptionCounter = function(){ + var count = 0; + var padding = 'deadbeefcafebabe'; + return { + next: function(){ + count++; + if (Number.MAX_VALUE === count) count = 0; + return padding + binpack.packUInt64(count, 'big').toString('hex'); + } + }; +}; + + +/** + * Defining each client that connects to the stratum server. + * Emits: + * - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size)) + * - 'submit' FIX THIS. +**/ +var StratumClient = function(options){ + + //private members + + var _this = this; + + (function init(){ + setupSocket(); + })(); + + function handleMessage(message){ + switch(message.method){ + case 'mining.subscribe': + handleSubscribe(message); + break; + case 'mining.authorize': + handleAuthorize(message); + break; + case 'mining.submit': + handleSubmit(message); + break; + case 'mining.get_transactions': + sendJson({ + id : null, + result : [], + error : true + }); + break; + default: + console.dir('unknown stratum client message: ' + JSON.stringify(message)); + break; + } + } + + function handleSubscribe(message){ + if (! _this._authorized ) { + _this.requestedSubscriptionBeforeAuth = true; + } + _this.emit('subscription', + {}, + function(error, extraNonce1, extraNonce2Size){ + if (error){ + sendJson({ + id: message.id, + result: null, + error: error + }); + return; + } + _this.extraNonce1 = extraNonce1; + sendJson({ + id: message.id, + result: [ + ["mining.notify", options.subscriptionId], + extraNonce1, + extraNonce2Size + ], + error: null + }); + } + ); + } + + function handleAuthorize(message){ + _this.workerIP = options.socket.address().address; + _this.workerName = message.params[0]; + _this.workerPass = message.params[1]; + options.authorizeFn(_this.workerIP, _this.workerName, _this.workerPass, function(result) { + _this.authorized = (!result.error && result.authorized); + sendJson({ + id : message.id, + result : _this.authorized, + error : result.error + }); + + // If the authorizer wants us to close the socket lets do it. + if (result.disconnect === true) { + options.socket.end(); + } + }); + } + + function handleSubmit(message){ + if (!_this.authorized){ + sendJson({ + id : message.id, + result: null, + error : [24, "unauthorized worker", null] + }); + return; + } + if (!_this.extraNonce1){ + sendJson({ + id : message.id, + result: null, + error : [25, "not subscribed", null] + }); + return; + } + _this.emit('submit', + { + name : message.params[0], + jobId : message.params[1], + extraNonce2 : message.params[2], + nTime : message.params[3], + nonce : message.params[4] + }, + function(error, result){ + sendJson({ + id : message.id, + result : result, + error : error + }); + } + ); + } + + function sendJson(){ + var response = ''; + for (var i = 0; i < arguments.length; i++){ + response += JSON.stringify(arguments[i]) + '\n'; + } + options.socket.write(response); + } + + function setupSocket(){ + var socket = options.socket; + var dataBuffer = ''; + socket.setEncoding('utf8'); + socket.on('data', function(d){ + console.log('request: ' + d); + dataBuffer += d; + if (dataBuffer.slice(-1) === '\n'){ + var messages = dataBuffer.split('\n'); + messages.forEach(function(message){ + if (message.trim() === '') return; + var messageJson; + try{ + messageJson = JSON.parse(message); + } + catch(e){ + console.log('could not parse stratum client socket message: ' + message); + } + if (messageJson) { + handleMessage(messageJson); + } + }); + dataBuffer = ''; + } + }); + socket.on('end', function() { + _this.emit('socketDisconnect') + console.log('stratum client disconnected'); + }); + socket.on('error', function(){ + _this.emit('socketError'); + console.log('stratum client socket error'); + }); + } + + + //public members + + /** + * IF the given difficulty is valid and new it'll send it to the client. + * returns boolean + **/ + this.sendAndSetDifficultyIfNew = function(difficulty){ + if (typeof(difficulty) != 'number') { + console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']'); + return false; + } + + if (difficulty !== this.difficulty) { + this.difficulty = difficulty; + sendJson({ + id : null, + method: "mining.set_difficulty", + params: [difficulty]//[512], + }); + return true; + } else { + return false; + } + + }; + + this.sendMiningJob = function(jobParams){ + sendJson({ + id : null, + method: "mining.notify", + params: jobParams + }); + }; +}; +StratumClient.prototype.__proto__ = events.EventEmitter.prototype; + + + +/** + * The actual stratum server. + * It emits the following Events: + * - 'client.connected'(StratumClientInstance) - when a new miner connects + * - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore. + * - 'started' - when the server is up and running + **/ +var StratumServer = exports.Server = function StratumServer(options){ + + //private members + + var _this = this; + var socketServer; + var stratumClients = {}; + var subscriptionCounter = SubscriptionCounter(); + + (function init(){ + _socketServer = socketServer = net.createServer(function(c){ + var subscriptionId = subscriptionCounter.next(); + var client = new StratumClient( + { + subscriptionId : subscriptionId, + socket : c, + authorizeFn : options.authorizeFn + } + ); + stratumClients[subscriptionId] = client; + _this.emit('client.connected', client); + client.on('socketDisconnect', function() { + delete stratumClients[subscriptionId]; + _this.emit('client.disconnected', client); + }); + }); + _socketServer.listen(options.port, function(){ + _this.emit('started'); + }); + })(); + + + //public members + + this.broadcastMiningJobs = function(jobParams) { + for (var clientId in stratumClients) { + // if a client gets disconnected WHILE doing this loop a crash might happen. + // 'm not sure if that can ever happn but an if here doesn't hurt! + if (typeof(stratumClients[clientId]) !== 'undefined') { + stratumClients[clientId].sendMiningJob(jobParams); + } + } + }; +}; StratumServer.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/libs/transactions.js b/lib/transactions.js similarity index 96% rename from libs/transactions.js rename to lib/transactions.js index 28c1259..495a7d7 100644 --- a/libs/transactions.js +++ b/lib/transactions.js @@ -1,110 +1,110 @@ -var binpack = require('binpack'); -var buffertools = require('buffertools'); - -var util = require('./util.js'); - - -function Transaction(params){ - - var version = params.version || 1, - inputs = params.inputs || [], - outputs = params.outputs || [], - lockTime = params.lockTime || 0; - - - this.toBuffer = function(){ - return Buffer.concat([ - binpack.packUInt32(version, 'little'), - util.varIntBuffer(inputs.length), - Buffer.concat(inputs.map(function(i){ return i.toBuffer() })), - util.varIntBuffer(outputs.length), - Buffer.concat(outputs.map(function(o){ return o.toBuffer() })), - binpack.packUInt32(lockTime, 'little') - ]); - }; - - this.inputs = inputs; - this.outputs = outputs; - -} - -function TransactionInput(params){ - - var prevOutHash = params.prevOutHash || 0, - prevOutIndex = params.prevOutIndex, - sigScript = params.sigScript, - sequence = params.sequence || 0; - - - this.toBuffer = function(){ - sigScriptBuffer = sigScript.toBuffer(); - return Buffer.concat([ - util.uint256BufferFromHash(prevOutHash), - binpack.packUInt32(prevOutIndex, 'little'), - util.varIntBuffer(sigScriptBuffer.length), - sigScriptBuffer, - binpack.packUInt32(sequence) - ]); - }; -} - -function TransactionOutput(params){ - - var value = params.value, - pkScriptBuffer = params.pkScriptBuffer; - - this.toBuffer = function(){ - return Buffer.concat([ - binpack.packInt64(value, 'little'), - util.varIntBuffer(pkScriptBuffer.length), - pkScriptBuffer - ]); - }; -} - -function ScriptSig(params){ - - var height = params.height, - flags = params.flags, - extraNoncePlaceholder = params.extraNoncePlaceholder; - - this.toBuffer = function(){ - - return Buffer.concat([ - util.serializeNumber(height), - new Buffer(flags, 'hex'), - util.serializeNumber(Date.now() / 1000 | 0), - new Buffer([extraNoncePlaceholder.length]), - extraNoncePlaceholder, - util.serializeString('/nodeStratum/') - ]); - } - -}; - -var Generation = exports.Generation = function Generation(rpcData, publicKey, extraNoncePlaceholder){ - - var tx = new Transaction({ - inputs: [new TransactionInput({ - prevOutIndex : Math.pow(2, 32) - 1, - sigScript : new ScriptSig({ - height : rpcData.height, - flags : rpcData.coinbaseaux.flags, - extraNoncePlaceholder : extraNoncePlaceholder - }) - })], - outputs: [new TransactionOutput({ - value : rpcData.coinbasevalue, - pkScriptBuffer : publicKey - })] - }); - - var txBuffer = tx.toBuffer(); - var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder); - var p1 = txBuffer.slice(0, epIndex); - var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length); - - this.transaction = tx; - this.coinbase = [p1, p2]; - +var binpack = require('binpack'); +var buffertools = require('buffertools'); + +var util = require('./util.js'); + + +function Transaction(params){ + + var version = params.version || 1, + inputs = params.inputs || [], + outputs = params.outputs || [], + lockTime = params.lockTime || 0; + + + this.toBuffer = function(){ + return Buffer.concat([ + binpack.packUInt32(version, 'little'), + util.varIntBuffer(inputs.length), + Buffer.concat(inputs.map(function(i){ return i.toBuffer() })), + util.varIntBuffer(outputs.length), + Buffer.concat(outputs.map(function(o){ return o.toBuffer() })), + binpack.packUInt32(lockTime, 'little') + ]); + }; + + this.inputs = inputs; + this.outputs = outputs; + +} + +function TransactionInput(params){ + + var prevOutHash = params.prevOutHash || 0, + prevOutIndex = params.prevOutIndex, + sigScript = params.sigScript, + sequence = params.sequence || 0; + + + this.toBuffer = function(){ + sigScriptBuffer = sigScript.toBuffer(); + return Buffer.concat([ + util.uint256BufferFromHash(prevOutHash), + binpack.packUInt32(prevOutIndex, 'little'), + util.varIntBuffer(sigScriptBuffer.length), + sigScriptBuffer, + binpack.packUInt32(sequence) + ]); + }; +} + +function TransactionOutput(params){ + + var value = params.value, + pkScriptBuffer = params.pkScriptBuffer; + + this.toBuffer = function(){ + return Buffer.concat([ + binpack.packInt64(value, 'little'), + util.varIntBuffer(pkScriptBuffer.length), + pkScriptBuffer + ]); + }; +} + +function ScriptSig(params){ + + var height = params.height, + flags = params.flags, + extraNoncePlaceholder = params.extraNoncePlaceholder; + + this.toBuffer = function(){ + + return Buffer.concat([ + util.serializeNumber(height), + new Buffer(flags, 'hex'), + util.serializeNumber(Date.now() / 1000 | 0), + new Buffer([extraNoncePlaceholder.length]), + extraNoncePlaceholder, + util.serializeString('/nodeStratum/') + ]); + } + +}; + +var Generation = exports.Generation = function Generation(rpcData, publicKey, extraNoncePlaceholder){ + + var tx = new Transaction({ + inputs: [new TransactionInput({ + prevOutIndex : Math.pow(2, 32) - 1, + sigScript : new ScriptSig({ + height : rpcData.height, + flags : rpcData.coinbaseaux.flags, + extraNoncePlaceholder : extraNoncePlaceholder + }) + })], + outputs: [new TransactionOutput({ + value : rpcData.coinbasevalue, + pkScriptBuffer : publicKey + })] + }); + + var txBuffer = tx.toBuffer(); + var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder); + var p1 = txBuffer.slice(0, epIndex); + var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length); + + this.transaction = tx; + this.coinbase = [p1, p2]; + }; \ No newline at end of file diff --git a/libs/util.js b/lib/util.js similarity index 96% rename from libs/util.js rename to lib/util.js index cf346b3..46d7d70 100644 --- a/libs/util.js +++ b/lib/util.js @@ -1,309 +1,309 @@ -var crypto = require('crypto'); - -var binpack = require('binpack'); -var base58 = require('base58-native'); -var bignum = require('bignum'); - - -exports.bignumFromBits = function(bitsString){ - var bitsBuff = new Buffer(bitsString, 'hex'); - var numBytes = bitsBuff.readUInt8(0); - var bigBits = bignum.fromBuffer(bitsBuff.slice(1)); - var target = bigBits.mul( - bignum(2).pow( - bignum(8).mul( - numBytes - 3 - ) - ) - ); - return target; -}; - -exports.bignumFromTarget = function(targetString){ - return bignum.fromBuffer(new Buffer(targetString, 'hex')); -}; - -exports.doublesha = function(buffer){ - var hash1 = crypto.createHash('sha256'); - hash1.update(buffer); - hash1 = hash1.digest(); - - var hash2 = crypto.createHash('sha256'); - hash2.update(hash1); - hash2 = hash2.digest(); - - return hash2; -}; - -exports.reverseBuffer = function(buff){ - var reversed = new Buffer(buff.length); - for (var i = buff.length - 1; i >= 0; i--) - reversed[buff.length - i - 1] = buff[i]; - return reversed; -}; - -exports.reverseHex = function(hex){ - return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex'); -}; - -exports.reverseByteOrder = function(buff){ - for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4); - return exports.reverseBuffer(buff); -}; - -exports.uint256BufferFromHash = function(hex){ - - var fromHex = new Buffer(hex, 'hex'); - - if (fromHex.length != 32){ - var empty = new Buffer(32); - empty.fill(0); - fromHex.copy(empty); - fromHex = empty; - } - - return exports.reverseBuffer(fromHex); -}; - -exports.hexFromReversedBuffer = function(buffer){ - return exports.reverseBuffer(buffer).toString('hex'); -}; - -exports.varIntBuffer = function(n){ - if (n < 0xfd) - return new Buffer([n]); - else if (n < 0xffff){ - var buff = new Buffer(3); - buff[0] = 0xfd; - buff.writeUInt16LE(n, 1); - return buff; - } - else if (n < 0xffffffff){ - var buff = new Buffer(5); - buff[0] = 0xfe; - buff.writeUInt32LE(n, 1); - return buff; - } - else{ - var buff = new Buffer(9); - buff[0] = 0xff; - binpack.packUInt64(n, 'little').copy(buff, 1); - return buff; - } -}; - -exports.serializeNumber = function(n){ - if (n < 0xfd){ - var buff = new Buffer(2); - buff[0] = 0x1; - buff.writeUInt8(n, 1); - return buff; - } - else if (n <= 0xffff){ - var buff = new Buffer(4); - buff[0] = 0x3; - buff.writeUInt16LE(n, 1); - return buff; - } - else if (n <= 0xffffffff){ - var buff = new Buffer(5); - buff[0] = 0x4; - buff.writeUInt32LE(n, 1); - return buff; - } - else{ - return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]); - } -}; - -exports.serializeString = function(s){ - - if (s.length < 253) - return Buffer.concat([ - new Buffer([s.length]), - new Buffer(s) - ]); - else if (s.length < 0x10000) - return Buffer.concat([ - new Buffer([253]), - binpack.packUInt16(s.length, 'little'), - new Buffer(s) - ]); - else if (s.length < 0x100000000) - return Buffer.concat([ - new Buffer([254]), - binpack.packUInt32(s.length, 'little'), - new Buffer(s) - ]); - else - return Buffer.concat([ - new Buffer([255]), - binpack.packUInt64(s.length), - new Buffer(s) - ]); -}; - -exports.range = function(start, stop, step){ - if (typeof stop === 'undefined'){ - stop = start; - start = 0; - } - if (typeof step === 'undefined'){ - step = 1; - } - if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){ - return []; - } - var result = []; - for (var i = start; step > 0 ? i < stop : i > stop; i += step){ - result.push(i); - } - return result; -}; - -exports.address_to_pubkeyhash = function(addr){ - addr = base58.decode(addr); - - if (addr.length != 25){ - console.log('invalid address length for ' + addr); - throw 'invalid address length'; - } - - if (!addr) - return null; - - var ver = addr[0]; - var cksumA = addr.slice(-4); - var cksumB = exports.doublesha(addr.slice(0, -4)).slice(0, 4); - - if (cksumA.toString('hex') != cksumB.toString('hex')) - throw 'checksum did not match'; - - return [ver, addr.slice(1,-4)]; -}; - -exports.script_to_pubkey = function(key){ - if (key.length === 66) key = new Buffer(key, 'hex'); - if (key !== 33) throw 'Invalid address'; - var pubkey = new Buffer(35); - pubkey[0] = 0x21; - pubkey[34] = 0xac; - key.copy(pubkey, 1); - return pubkey; -}; - -exports.script_to_address = function(addr){ - var d = exports.address_to_pubkeyhash(addr) - if (!d) - throw "invalid address"; - - var ver = d[0]; - var pubkeyhash = d[1]; - return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkeyhash, new Buffer([0x88, 0xac])]); -}; - - -/* -exports.makeBufferReadable = function(buffer){ - var position = 0; - buffer.read = function(length){ - var section = buffer.slice(position, length ? (position + length) : buffer.length); - position += length; - return MakeBufferReadable(section); - } - return buffer; -}; - - -exports.ser_uint256 = function(u){ - var rs = new Buffer(0); - exports.range(8).forEach(function(i){ - rs = Buffer.concat([ - rs, - binpack.packUInt32(u & 0xFFFFFFFF, 'little') - ]); - u >>= 32; - }); - return rs; -}; - -exports.deser_uint256 = function(f){ - var r = 0; - exports.range(8).forEach(function(i){ - var t = f.read(4).readUInt32LE(4); - r += t << (i * 32); - }); - return r; -}; - -exports.uint256_from_compact = function(c){ - var nbytes = (c >> 24) & 0xFF; - v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) - return v; -}; - -exports.uint256_from_str = function(s){ - var r = 0; - var t = binpack.unpack -}; - -exports.ser_uint256_be = function(u){ - var rs = new Buffer(0); - exports.range(8).forEach(function(i){ - rs = Buffer.concat([ - rs, - binpack.packUInt32(u & 0xFFFFFFFF, 'big') - ]); - u >>= 32; - }); - return rs; -}; - -exports.deser_string = function(f){ - var nit = f.read(1).readUInt8(0); - if (nit == 253) - nit = f.read(2).readUInt16LE(0); - else if (nit == 254) - nit = f.read(4).readUInt32LE(1); - else if (nit == 255) - nit = f.read(8).readUInt64LE(1); - return f.read(nit); -}; - - -exports.ser_vector = function(l){ - var r; - if (l.length < 253) - r = new Buffer([l.length]); - else if (l.length < 0x10000) - r = Buffer.concat([new Buffer([253]), binpack.packUInt16(l.length, 'little')]); - else if (l.length < 0x100000000) - r = Buffer.concat([new Buffer([254]), binpack.packUInt32(l.length, 'little')]); - else - r = Buffer.concat([new Buffer([255]), binpack.packUInt64(l.length, 'little')]); - - l.forEach(function(i){ - r = Buffer.concat([r, i.serialize()]); - }); - - return r; -}; - -exports.deser_vector = function(f, c){ - var nit = f.read(1).readUInt8(0); - if (nit == 253) - nit = f.read(2).readUInt16LE(0); - else if (nit == 254) - nit = f.read(4).readUInt32LE(0); - else if (nit == 255) - nit = f.read(8).readUInt64LE(0); - var r = []; - exports.range(nit).forEach(function(i){ - var t = new c(); - t.deserialize(f); - r.push(t); - }); - return r; -}; - */ - +var crypto = require('crypto'); + +var binpack = require('binpack'); +var base58 = require('base58-native'); +var bignum = require('bignum'); + + +exports.bignumFromBits = function(bitsString){ + var bitsBuff = new Buffer(bitsString, 'hex'); + var numBytes = bitsBuff.readUInt8(0); + var bigBits = bignum.fromBuffer(bitsBuff.slice(1)); + var target = bigBits.mul( + bignum(2).pow( + bignum(8).mul( + numBytes - 3 + ) + ) + ); + return target; +}; + +exports.bignumFromTarget = function(targetString){ + return bignum.fromBuffer(new Buffer(targetString, 'hex')); +}; + +exports.doublesha = function(buffer){ + var hash1 = crypto.createHash('sha256'); + hash1.update(buffer); + hash1 = hash1.digest(); + + var hash2 = crypto.createHash('sha256'); + hash2.update(hash1); + hash2 = hash2.digest(); + + return hash2; +}; + +exports.reverseBuffer = function(buff){ + var reversed = new Buffer(buff.length); + for (var i = buff.length - 1; i >= 0; i--) + reversed[buff.length - i - 1] = buff[i]; + return reversed; +}; + +exports.reverseHex = function(hex){ + return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex'); +}; + +exports.reverseByteOrder = function(buff){ + for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4); + return exports.reverseBuffer(buff); +}; + +exports.uint256BufferFromHash = function(hex){ + + var fromHex = new Buffer(hex, 'hex'); + + if (fromHex.length != 32){ + var empty = new Buffer(32); + empty.fill(0); + fromHex.copy(empty); + fromHex = empty; + } + + return exports.reverseBuffer(fromHex); +}; + +exports.hexFromReversedBuffer = function(buffer){ + return exports.reverseBuffer(buffer).toString('hex'); +}; + +exports.varIntBuffer = function(n){ + if (n < 0xfd) + return new Buffer([n]); + else if (n < 0xffff){ + var buff = new Buffer(3); + buff[0] = 0xfd; + buff.writeUInt16LE(n, 1); + return buff; + } + else if (n < 0xffffffff){ + var buff = new Buffer(5); + buff[0] = 0xfe; + buff.writeUInt32LE(n, 1); + return buff; + } + else{ + var buff = new Buffer(9); + buff[0] = 0xff; + binpack.packUInt64(n, 'little').copy(buff, 1); + return buff; + } +}; + +exports.serializeNumber = function(n){ + if (n < 0xfd){ + var buff = new Buffer(2); + buff[0] = 0x1; + buff.writeUInt8(n, 1); + return buff; + } + else if (n <= 0xffff){ + var buff = new Buffer(4); + buff[0] = 0x3; + buff.writeUInt16LE(n, 1); + return buff; + } + else if (n <= 0xffffffff){ + var buff = new Buffer(5); + buff[0] = 0x4; + buff.writeUInt32LE(n, 1); + return buff; + } + else{ + return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]); + } +}; + +exports.serializeString = function(s){ + + if (s.length < 253) + return Buffer.concat([ + new Buffer([s.length]), + new Buffer(s) + ]); + else if (s.length < 0x10000) + return Buffer.concat([ + new Buffer([253]), + binpack.packUInt16(s.length, 'little'), + new Buffer(s) + ]); + else if (s.length < 0x100000000) + return Buffer.concat([ + new Buffer([254]), + binpack.packUInt32(s.length, 'little'), + new Buffer(s) + ]); + else + return Buffer.concat([ + new Buffer([255]), + binpack.packUInt64(s.length), + new Buffer(s) + ]); +}; + +exports.range = function(start, stop, step){ + if (typeof stop === 'undefined'){ + stop = start; + start = 0; + } + if (typeof step === 'undefined'){ + step = 1; + } + if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){ + return []; + } + var result = []; + for (var i = start; step > 0 ? i < stop : i > stop; i += step){ + result.push(i); + } + return result; +}; + +exports.address_to_pubkeyhash = function(addr){ + addr = base58.decode(addr); + + if (addr.length != 25){ + console.log('invalid address length for ' + addr); + throw 'invalid address length'; + } + + if (!addr) + return null; + + var ver = addr[0]; + var cksumA = addr.slice(-4); + var cksumB = exports.doublesha(addr.slice(0, -4)).slice(0, 4); + + if (cksumA.toString('hex') != cksumB.toString('hex')) + throw 'checksum did not match'; + + return [ver, addr.slice(1,-4)]; +}; + +exports.script_to_pubkey = function(key){ + if (key.length === 66) key = new Buffer(key, 'hex'); + if (key !== 33) throw 'Invalid address'; + var pubkey = new Buffer(35); + pubkey[0] = 0x21; + pubkey[34] = 0xac; + key.copy(pubkey, 1); + return pubkey; +}; + +exports.script_to_address = function(addr){ + var d = exports.address_to_pubkeyhash(addr) + if (!d) + throw "invalid address"; + + var ver = d[0]; + var pubkeyhash = d[1]; + return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkeyhash, new Buffer([0x88, 0xac])]); +}; + + +/* +exports.makeBufferReadable = function(buffer){ + var position = 0; + buffer.read = function(length){ + var section = buffer.slice(position, length ? (position + length) : buffer.length); + position += length; + return MakeBufferReadable(section); + } + return buffer; +}; + + +exports.ser_uint256 = function(u){ + var rs = new Buffer(0); + exports.range(8).forEach(function(i){ + rs = Buffer.concat([ + rs, + binpack.packUInt32(u & 0xFFFFFFFF, 'little') + ]); + u >>= 32; + }); + return rs; +}; + +exports.deser_uint256 = function(f){ + var r = 0; + exports.range(8).forEach(function(i){ + var t = f.read(4).readUInt32LE(4); + r += t << (i * 32); + }); + return r; +}; + +exports.uint256_from_compact = function(c){ + var nbytes = (c >> 24) & 0xFF; + v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) + return v; +}; + +exports.uint256_from_str = function(s){ + var r = 0; + var t = binpack.unpack +}; + +exports.ser_uint256_be = function(u){ + var rs = new Buffer(0); + exports.range(8).forEach(function(i){ + rs = Buffer.concat([ + rs, + binpack.packUInt32(u & 0xFFFFFFFF, 'big') + ]); + u >>= 32; + }); + return rs; +}; + +exports.deser_string = function(f){ + var nit = f.read(1).readUInt8(0); + if (nit == 253) + nit = f.read(2).readUInt16LE(0); + else if (nit == 254) + nit = f.read(4).readUInt32LE(1); + else if (nit == 255) + nit = f.read(8).readUInt64LE(1); + return f.read(nit); +}; + + +exports.ser_vector = function(l){ + var r; + if (l.length < 253) + r = new Buffer([l.length]); + else if (l.length < 0x10000) + r = Buffer.concat([new Buffer([253]), binpack.packUInt16(l.length, 'little')]); + else if (l.length < 0x100000000) + r = Buffer.concat([new Buffer([254]), binpack.packUInt32(l.length, 'little')]); + else + r = Buffer.concat([new Buffer([255]), binpack.packUInt64(l.length, 'little')]); + + l.forEach(function(i){ + r = Buffer.concat([r, i.serialize()]); + }); + + return r; +}; + +exports.deser_vector = function(f, c){ + var nit = f.read(1).readUInt8(0); + if (nit == 253) + nit = f.read(2).readUInt16LE(0); + else if (nit == 254) + nit = f.read(4).readUInt32LE(0); + else if (nit == 255) + nit = f.read(8).readUInt64LE(0); + var r = []; + exports.range(nit).forEach(function(i){ + var t = new c(); + t.deserialize(f); + r.push(t); + }); + return r; +}; + */ + diff --git a/node_modules/bignum/package.json b/node_modules/bignum/package.json index 98a10f2..5de79b2 100644 --- a/node_modules/bignum/package.json +++ b/node_modules/bignum/package.json @@ -2,7 +2,7 @@ "name": "bignum", "version": "0.6.2", "description": "Arbitrary-precision integer arithmetic using OpenSSL", - "main": "./index.js", + "main": "./pool.js", "repository": { "type": "git", "url": "http://github.com/justmoon/node-bignum.git" diff --git a/node_modules/binpack/node_modules/bindings/bindings.js b/node_modules/binpack/node_modules/bindings/bindings.js index a40029b..552117f 100644 --- a/node_modules/binpack/node_modules/bindings/bindings.js +++ b/node_modules/binpack/node_modules/bindings/bindings.js @@ -131,7 +131,7 @@ exports.getFileName = function getFileName () { * somewhere in the module tree. The "root directory" is the directory * containing the `package.json` file. * - * In: /home/nate/node-native-module/lib/index.js + * In: /home/nate/node-native-module/lib/pool.js * Out: /home/nate/node-native-module */ diff --git a/node_modules/quark-hash/node_modules/bindings/bindings.js b/node_modules/quark-hash/node_modules/bindings/bindings.js index a40029b..552117f 100644 --- a/node_modules/quark-hash/node_modules/bindings/bindings.js +++ b/node_modules/quark-hash/node_modules/bindings/bindings.js @@ -131,7 +131,7 @@ exports.getFileName = function getFileName () { * somewhere in the module tree. The "root directory" is the directory * containing the `package.json` file. * - * In: /home/nate/node-native-module/lib/index.js + * In: /home/nate/node-native-module/lib/pool.js * Out: /home/nate/node-native-module */ diff --git a/node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js b/node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js index a40029b..552117f 100644 --- a/node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js +++ b/node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js @@ -131,7 +131,7 @@ exports.getFileName = function getFileName () { * somewhere in the module tree. The "root directory" is the directory * containing the `package.json` file. * - * In: /home/nate/node-native-module/lib/index.js + * In: /home/nate/node-native-module/lib/pool.js * Out: /home/nate/node-native-module */ diff --git a/node_modules/scrypt256-hash/node_modules/bindings/bindings.js b/node_modules/scrypt256-hash/node_modules/bindings/bindings.js index a40029b..552117f 100644 --- a/node_modules/scrypt256-hash/node_modules/bindings/bindings.js +++ b/node_modules/scrypt256-hash/node_modules/bindings/bindings.js @@ -131,7 +131,7 @@ exports.getFileName = function getFileName () { * somewhere in the module tree. The "root directory" is the directory * containing the `package.json` file. * - * In: /home/nate/node-native-module/lib/index.js + * In: /home/nate/node-native-module/lib/pool.js * Out: /home/nate/node-native-module */ diff --git a/package.json b/package.json new file mode 100644 index 0000000..a1ea223 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "http-server", + "version": "0.0.1", + "author": "Matthew Little", + "description": "High performance Stratum poolserver in Node.js", + "contributors": [ + "vekexasia" + ], + "bin": { + "block-notify": "./scripts/blockNotify.js" + }, + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/zone117x/node-stratum.git" + }, + "keywords": [ + "stratum", + "pool", + "server", + "poolserver", + "bitcoin", + "litecoin", + "scrypt" + ], + "bundledDependencies": [ + "scrypt256-hash", + "scrypt-jane-hash", + "quark-hash", + "binpack", + "bignum", + "buffertools", + "base58-native" + ], + "license": "GPL-2.0", + "engines": { + "node": ">=0.10" + } +} \ No newline at end of file