diff --git a/TODOLIST.md b/TODOLIST.md new file mode 100644 index 0000000..97523d8 --- /dev/null +++ b/TODOLIST.md @@ -0,0 +1,11 @@ + +#### TODO + * vekexasia: handle socket error from client so that we could clean up stratumServers Client + * vekexasia: fix check of difficulty of the shares. Not sure why a lot of shares are marked as "low-submit-share" + +#### TOFIX + * vekexasia: jobManager.js has implement the expected block hash after submitting it to the daemon. This will let us check if the block was accepted by the daemon. + + + +#### DONE \ No newline at end of file diff --git a/blockNotify.js b/blockNotify.js deleted file mode 100644 index deea49e..0000000 --- a/blockNotify.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node - -var net = require('net'); - -var config = process.argv[1]; -var parts = config.split(':'); -var host = parts[0]; -var port = parts[1]; -var password = process.argv[2]; -var coin = process.argv[3]; -var blockHash = process.argv[4]; - -var client = net.connect(port, host, function() { - console.log('client connected'); - client.write(JSON.stringify({ - password: password, - coin: coin, - blockHash: blockHash - }) + '\n'); -}); - -client.on('data', function(data) { - console.log(data.toString()); - //client.end(); -}); - -client.on('end', function() { - console.log('client disconnected'); - //process.exit(); -}); \ No newline at end of file diff --git a/coins/dogecoin.json b/example/coins/dogecoin.json similarity index 78% rename from coins/dogecoin.json rename to example/coins/dogecoin.json index d108652..b9fdae4 100644 --- a/coins/dogecoin.json +++ b/example/coins/dogecoin.json @@ -10,8 +10,9 @@ "merkleRefreshInterval": 60, "daemon": { "host": "localhost", - "port": 8332, - "user": "test", - "password": "test" + "port": 19334, + "user": "testnet", + "password": "testnet1" + } -} \ No newline at end of file +} diff --git a/config.json b/example/config.json similarity index 100% rename from config.json rename to example/config.json diff --git a/init.js b/example/init.js similarity index 75% rename from init.js rename to example/init.js index 74763c0..cad6ce3 100644 --- a/init.js +++ b/example/init.js @@ -1,8 +1,7 @@ -var net = require('net'); -var fs = require('fs'); -var path = require('path'); - -var pool = require('./pool.js'); +var net = require('net'); +var fs = require('fs'); +var path = require('path'); +var pool = require('../index.js'); var ShareManager = require('./shareManager.js').ShareManager; var logRef = console.log; @@ -24,6 +23,18 @@ var coins = []; var confFolder = 'coins'; + +var authorizeFN = function (ip, workerName, password, cback) { + // Default implementation just returns true + console.log("Athorize ["+ip+"] "+workerName+":"+password); + cback( + null, // error + true, // authorized? + false, // should disconnect user? + 16 // difficulty + ); +} + fs.readdir(confFolder, function(err, files){ if (err) throw err; files.forEach(function(file){ @@ -35,8 +46,8 @@ fs.readdir(confFolder, function(err, files){ var coin = new Coin(coinJson); console.log('Starting pool for ' + coin.options.name); - coin.pool = new pool(coin); - coin.shareManager = new ShareManager(coin.pool); + coin.pool = new pool(coin, authorizeFN ); + var shareManager = new ShareManager(coin.pool); coins.push(coin); }); diff --git a/shareManager.js b/example/shareManager.js similarity index 55% rename from shareManager.js rename to example/shareManager.js index 636aa39..85fb296 100644 --- a/shareManager.js +++ b/example/shareManager.js @@ -11,28 +11,27 @@ var ShareManager = exports.ShareManager = function(pool) { pool.on('share', function(isValid, data) { if (isValid) { handleValidShare( - data.workerName, + data.client, data.blockHeaderHex, data.jobId, - data.clientDifficulty, data.extraNonce1, data.extraNonce2, data.nTime, data.nonce); } else { handleInvalidShare( - data.workerName, + data.client, data.error[0], data.error[1]); } }); - function handleValidShare(workerName, headerHex, jobId, clientDifficulty, extraNonce1, extraNonce2, nTime, nonce) { - console.log("A new Valid share from "+workerName+" has arrived! - "+headerHex); + function handleValidShare(client, headerHex, jobId, extraNonce1, extraNonce2, nTime, nonce) { + console.log("A new Valid share from "+client.workerName+" has arrived! - "+headerHex); } - function handleInvalidShare(workerName, errorCode, errorDescription) { - console.log("Invalid share form "+workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription); + function handleInvalidShare(client, errorCode, errorDescription) { + console.log("Invalid share form "+client.workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription); } }; diff --git a/pool.js b/index.js similarity index 81% rename from pool.js rename to index.js index 4e48541..7ca5ed1 100644 --- a/pool.js +++ b/index.js @@ -1,15 +1,18 @@ -var net = require('net'); -var events = require('events'); -var fs = require('fs'); +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 async = require('async'); - -var daemon = require('./daemon.js'); -var stratum = require('./stratum.js'); -var jobManager = require('./jobManager.js'); -var util = require('./util.js'); - -var pool = module.exports = function pool(coin){ +/** + * 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, authFn){ var _this = this; var publicKeyBuffer; @@ -114,26 +117,26 @@ var pool = module.exports = function pool(coin){ 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 + 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', function(client){ + }).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.sendDifficulty(coin.options.difficulty); - if (typeof(_this.jobManager.currentJob) === 'undefined') { - console.warn("[subscription] Cannot send job to client. No jobs in jobManager!"); - } else { - this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); - } - }).on('authorize', function(params, resultCallback){ - resultCallback(null, true); + var clientThis = this; + + //if (clientThis.authorized) { + clientThis.sendMiningJob(_this.jobManager.currentJob.getJobParams()); + //} + }).on('submit', function(params, resultCallback){ var result =_this.jobManager.processShare( params.jobId, @@ -146,20 +149,19 @@ var pool = module.exports = function pool(coin){ if (result.error){ resultCallback(result.error); _this.emit('share', false, { - workerName : params.name, + client : client, error : result.error }); } else { resultCallback(null, true); _this.emit('share', true, { - blockHeaderHex : result.headerHEX, - workerName : params.name, - jobId : params.jobId, - clientDifficulty : client.difficulty, - extraNonce1 : client.extraNonce1, - extraNonce2 : params.extraNonce2, - nTime : params.nTime, - nonce : params.nonce + client : client, + blockHeaderHex : result.headerHEX, + workerName : params.name, + jobId : params.jobId, + extraNonce2 : params.extraNonce2, + nTime : params.nTime, + nonce : params.nonce }); } diff --git a/blockTemplate.js b/libs/blockTemplate.js similarity index 90% rename from blockTemplate.js rename to libs/blockTemplate.js index 8a4ffe1..396b1d2 100644 --- a/blockTemplate.js +++ b/libs/blockTemplate.js @@ -1,11 +1,13 @@ - -var binpack = require('binpack'); - -var merkleTree = require('./merkleTree.js'); +var binpack = require('binpack'); +var merkleTree = require('./merkleTree.js'); var transactions = require('./transactions.js'); -var util = require('./util.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 diff --git a/daemon.js b/libs/daemon.js similarity index 74% rename from daemon.js rename to libs/daemon.js index 1453da8..9f17e55 100644 --- a/daemon.js +++ b/libs/daemon.js @@ -1,10 +1,16 @@ -var http = require('http'); -var cp = require('child_process'); -var events = require('events'); - - +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){ @@ -61,11 +67,11 @@ function DaemonInterface(options){ } var options = { - hostname: 'localhost', - port: _this.options.port, - method: 'POST', - auth: _this.options.user + ':' + _this.options.password, - headers: { + 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 } }; diff --git a/jobManager.js b/libs/jobManager.js similarity index 72% rename from jobManager.js rename to libs/jobManager.js index 098b9ea..91dde87 100644 --- a/jobManager.js +++ b/libs/jobManager.js @@ -44,7 +44,11 @@ var JobCounter = function(){ }; }; - +/** + * 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 @@ -102,6 +106,25 @@ var JobManager = module.exports = function JobManager(options){ } })(); + /** + * 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(); @@ -128,45 +151,55 @@ var JobManager = module.exports = function JobManager(options){ return {error: [20, 'incorrect size of extranonce2', null]}; var job = jobs[jobId]; - if (!job) + if (!job) { return {error: [21, 'job not found', null]}; + } - if (nTime.length !== 8) + if (nTime.length !== 8) { return {error: [20, 'incorrect size of ntime']}; + } var nTimeInt = parseInt(nTime, 16); - if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) + if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) { return {error: [20, 'ntime out of range', null]}; + } - if (nonce.length !== 8) + if (nonce.length !== 8) { return {error: [20, 'incorrect size of nonce']}; + } - if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, 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 coinbaseHash = util.doublesha(coinbaseBuffer); var merkleRoot = job.merkleTree.withFirst(coinbaseHash); - merkleRoot = util.reverseBuffer(merkleRoot).toString('hex'); + merkleRoot = util.reverseBuffer(merkleRoot).toString('hex'); var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce); - var headerHash = hashDigest(headerBuffer, nTimeInt); + var headerHash = hashDigest(headerBuffer, nTimeInt); var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32}); - var targetUser = bignum(diffDividend / difficulty); - if (headerBigNum.gt(targetUser)){ - return {error: [23, 'low difficulty share', null]}; - } + 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); - _this.emit('blockFound', blockBuf.toString('hex'), headerBigNum.toString(16), coinbaseHash.toString('hex')); + if (headerBigNum.gt(targetUser)){ + return {error: [23, 'low difficulty share', null]}; + } } return {result: true, headerHEX: headerBigNum.toString(16)}; diff --git a/merkleTree.js b/libs/merkleTree.js similarity index 100% rename from merkleTree.js rename to libs/merkleTree.js diff --git a/libs/stratum.js b/libs/stratum.js new file mode 100644 index 0000000..cda177c --- /dev/null +++ b/libs/stratum.js @@ -0,0 +1,301 @@ +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); + } + } + }; +}; +StratumServer.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/transactions.js b/libs/transactions.js similarity index 79% rename from transactions.js rename to libs/transactions.js index e67cf88..28c1259 100644 --- a/transactions.js +++ b/libs/transactions.js @@ -86,23 +86,23 @@ var Generation = exports.Generation = function Generation(rpcData, publicKey, ex 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 + 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 + 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); + 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]; diff --git a/util.js b/libs/util.js similarity index 100% rename from util.js rename to libs/util.js diff --git a/scripts/blockNotify.js b/scripts/blockNotify.js new file mode 100644 index 0000000..37e7909 --- /dev/null +++ b/scripts/blockNotify.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node +/** + * This script should be hooked to the coin daemon as follow: + * + * litecoind -blocknotify="/path/to/this/script/blockNotify.js localhost:8117 password litecoin %s" + * + * The above will send tell litecoin to launch this script with those parameters every time + * a block is found. + * This script will then send the blockhash along with other informations to a listening tcp socket +**/ + +var net = require('net'); +var config = process.argv[1]; +var parts = config.split(':'); +var host = parts[0]; +var port = parts[1]; +var password = process.argv[2]; +var coin = process.argv[3]; +var blockHash = process.argv[4]; + +var client = net.connect(port, host, function() { + console.log('client connected'); + client.write(JSON.stringify({ + password: password, + coin: coin, + blockHash: blockHash + }) + '\n'); +}); + +client.on('data', function(data) { + console.log(data.toString()); + //client.end(); +}); + +client.on('end', function() { + console.log('client disconnected'); + //process.exit(); +}); \ No newline at end of file diff --git a/stratum.js b/stratum.js deleted file mode 100644 index 2486600..0000000 --- a/stratum.js +++ /dev/null @@ -1,229 +0,0 @@ -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'); - } - }; -}; - -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; - default: - console.dir('unknown stratum client message: ' + message); - break; - } - } - - function handleSubscribe(message){ - _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.emit('authorize', - { - name: message.params[0][0], - password: message.params[0][1] - }, - function(error, result){ - _this.authorized = result; - /*if (_this.authorized) { - // if authorized lets store the workername - // so that when a share is found we can get it for the accounting - _this.workerName = message.params[0][0]; - }*/ - - sendJson({ - id: message.id, - result: result, - error: error - }); - } - ); - } - - 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; - } - console.log("SUBMIT "+JSON.stringify(message)); - _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'; - } - console.log('response: ' + response); - 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 - - this.sendDifficulty = function(difficulty){ - _this.difficulty = difficulty; - sendJson({ - id: null, - method: "mining.set_difficulty", - params: [difficulty]//[512], - }); - }; - this.sendMiningJob = function(jobParams){ - sendJson({ - id: null, - method: "mining.notify", - params: jobParams - }); - }; -}; -StratumClient.prototype.__proto__ = events.EventEmitter.prototype; - - - -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}); - stratumClients[subscriptionId] = client; - _this.emit('client', client); - }); - _socketServer.listen(options.port, function(){ - _this.emit('started'); - }); - })(); - - - //public members - - this.broadcastMiningJobs = function(jobParams){ - for (var clientId in stratumClients){ - stratumClients[clientId].sendMiningJob(jobParams) - } - }; -}; -StratumServer.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/test.js b/test.js deleted file mode 100644 index 3dd736a..0000000 --- a/test.js +++ /dev/null @@ -1,121 +0,0 @@ -var bignum = require('bignum'); -var scrypt = require('scrypt256-hash'); - - -var 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; -}; - -/* -var hash = new Buffer("38f3e68be0b74813af175b8da506dfa3c3017ff06fed7ae85e3efee655c9f7fd", 'hex'); -var goal = "8be6f3381348b7e08d5b17afa3df06a5f07f01c3e87aed6fe6fe3e5efdf7c955"; - -var s = scrypt.digest(hash); -console.log(s.toString('hex')); -for (var i = 0; i < 20; i++) s.writeUInt32LE(s.readUInt32BE(i * 4), i * 4); - - -var nHash = new Buffer(hash.length); -for (var i = 0; i < 8; i++) nHash.writeUInt32LE(hash.readUInt32BE(i * 4), i * 4); -console.log('maybe: ' + nHash.toString('hex')); - - -var wow = bignum.fromBuffer(hash, {endian: 'little', size: 32}).toBuffer({endian: 'big', size: 32}).toString('hex'); -console.log(wow); -console.log(wow == goal ? 'good' : 'fuck'); -*/ - - -/* -var bb = new Buffer('0100000017a93c491d6e309cc53604cc32829a9610a95835e042f7c86a0b4455b8f5fbfe38f3e68be0b74813af175b8da506dfa3c3017ff06fed7ae85e3efee655c9f7fdb931ce520334011c000028cf', 'hex'); -var hash = scrypt.digest(bb); -console.log(bignum.fromBuffer(hash, {endian: 'little', size: 32}).toString()); -*? - -/* -var block = { - hash: "409fd235e2fdc7182db92e13eed1b352081d9013ddc90e0acd817e378b8c1d1a", - confirmations: 1, - size: 1913, - height: 493856, - version: 1, - merkleroot: "71669b50622da76f4d6940912e997b537006324e8a32ed06e8072c68abcd358f", - tx: [ - "0d93c30ddc4b4802ec4724b238d730b7084ba19456ca5eeda8add1e4f86afab0", - "79f0461b1b2596381eff80cc1b4929eb908d6f6f010f082d7f4762b8d8e8c573", - "91b8d68ff49310174ceeefb2bb13765552676028108119ef2f8e5b29c8ed2487", - "c02a588d2296c13d1d6aec449af916a966880b6f359d841e3f09df554ade28ac", - "d043e490e4632fa48ac6fc3cbd6544e02116db39598aadb500fa7e3594bef9e3", - "f839b2688042cb5b6338f982a7c1179f0f533d1ca219aa12537acac0c1a3f863" - ], - time: 1389202213, - nonce: 1370998784, - bits: "1d011a75", - difficulty: 0.90631872, - previousblockhash: "be11244bda34c4c08c23fa7c61a5445f6daab25049a2c75b91309b58a68d9083" -}; - -var phpResult = "0100000083908da6589b30915bc7a24950b2aa6d5f44a5617cfa238cc0c434da4b2411be8f35cdab682c07e806ed328a4e320670537b992e9140694d6fa72d62509b6671258bcd52751a011d00c8b751"; - - - -var header = new Buffer(80); -var position = 0; -header.writeUInt32BE(block.nonce, position); -header.write(block.bits, position += 4, 4, 'hex'); -header.writeUInt32BE(block.time, position += 4); -header.write(block.merkleroot, position += 4, 32, 'hex'); -header.write(block.previousblockhash, position += 32, 32, 'hex'); -header.writeUInt32BE(block.version, position += 32); -var header = reverseBuffer(header); - - - -if (phpResult === header.toString('hex')) - console.log('works!!!!!'); -else - console.log('fuck'); - */ - -nonce = "cf280000"; -bits = "1c013403"; -time = "52ce31b9"; -merkleroot = "38f3e68be0b74813af175b8da506dfa3c3017ff06fed7ae85e3efee655c9f7fd"; -pbh = "fefbf5b855440b6ac8f742e03558a910969a8232cc0436c59c306e1d493ca917"; -version = 1; - -merkleroot = reverseBuffer(new Buffer(merkleroot, 'hex')).toString('hex'); - -var serializeHeader = function(){ - var header = new Buffer(80); - var position = 0; - header.write(nonce, position, 4, 'hex'); - header.write(bits, position += 4, 4, 'hex'); - header.write(time, position += 4, 4, 'hex'); - header.write(merkleroot, position += 4, 32, 'hex'); - header.write(pbh, position += 32, 32, 'hex'); - header.writeUInt32BE(version, position + 32); - var header = reverseBuffer(header); - return header; -}; - -var headerBuff = serializeHeader(); -var headerHashed = scrypt.digest(headerBuff); -var hashInt = bignum.fromBuffer(headerHashed, {endian: 'little', size: 32}); - - -//var diffDividend = bignum.fromBuffer(new Buffer('0000ffff00000000000000000000000000000000000000000000000000000000'), 'hex'); -//var target = diffDividend.div(16) - -var dividend = 0x0000ffff00000000000000000000000000000000000000000000000000000000; -var target = dividend / 16; - -var big = bignum(target); - -console.log('dividend ' + big.toString()); - -console.log('hash ' + hashInt.toString()) -console.log('target ' + target.toString()); \ No newline at end of file