diff --git a/TODOLIST.md b/TODOLIST.md new file mode 100644 index 0000000..264693f --- /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/example/init.js b/example/init.js index 334c066..98033c9 100644 --- a/example/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('../index.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; @@ -48,7 +47,7 @@ fs.readdir(confFolder, function(err, files){ console.log('Starting pool for ' + coin.options.name); coin.pool = new pool(coin, authorizeFN ); - coin.shareManager = new ShareManager(coin.pool); + var shareManager = new ShareManager(coin.pool); coins.push(coin); @@ -75,7 +74,7 @@ fs.readdir(confFolder, function(err, files){ if (config.blockNotifyListener.enabled){ - console.log("ENABLED"); + console.log("blockNotifyListener ENABLED"); var blockNotifyServer = net.createServer(function(c) { console.log('server connected'); var data = ''; diff --git a/example/shareManager.js b/example/shareManager.js index 636aa39..85fb296 100644 --- a/example/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/index.js b/index.js index 111009c..c26d5f3 100644 --- a/index.js +++ b/index.js @@ -17,7 +17,6 @@ var pool = module.exports = function pool(coin, authFn){ var _this = this; var publicKeyBuffer; - this.shareManager = undefined; // just for us to know that the variable should be this one. this.jobManager = new jobManager({ algorithm: coin.options.algorithm, address: coin.options.address @@ -34,9 +33,6 @@ var pool = module.exports = function pool(coin, authFn){ process.exit("ERROR: an authorize Function is needed.") } - - - this.jobManager.on('newBlock', function(blockTemplate){ if ( typeof(_this.stratumServer ) === 'undefined') { console.warn("Stratum server still not started! cannot broadcast block!"); @@ -128,7 +124,7 @@ var pool = module.exports = function pool(coin, authFn){ _this.stratumServer.on('started', function(){ _this.emit('started'); 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(); @@ -155,17 +151,16 @@ var pool = module.exports = function pool(coin, authFn){ 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, { + client : client, blockHeaderHex : result.headerHEX, workerName : params.name, jobId : params.jobId, - clientDifficulty : client.difficulty, - extraNonce1 : client.extraNonce1, extraNonce2 : params.extraNonce2, nTime : params.nTime, nonce : params.nonce diff --git a/libs/jobManager.js b/libs/jobManager.js index 7cce92c..1d4fcf1 100644 --- a/libs/jobManager.js +++ b/libs/jobManager.js @@ -106,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(); @@ -131,45 +150,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); + + if (headerBigNum.gt(targetUser)){ + return {error: [23, 'low difficulty share', null]}; + } } return {result: true, headerHEX: headerBigNum.toString(16)}; diff --git a/libs/stratum.js b/libs/stratum.js index a8e3f7b..cda177c 100644 --- a/libs/stratum.js +++ b/libs/stratum.js @@ -46,8 +46,15 @@ var StratumClient = function(options){ case 'mining.submit': handleSubmit(message); break; + case 'mining.get_transactions': + sendJson({ + id : null, + result : [], + error : true + }); + break; default: - console.dir('unknown stratum client message: ' + message); + console.dir('unknown stratum client message: ' + JSON.stringify(message)); break; } } @@ -82,10 +89,10 @@ var StratumClient = function(options){ } 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.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, @@ -105,12 +112,12 @@ var StratumClient = function(options){ console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err)); options.socket.end(); } else { - _this.sendDifficulty(diff); + _this.sendAndSetDifficultyIfNew(diff); } }); } else if (typeof(difficulty) === 'number') { - _this.sendDifficulty(difficulty); + _this.sendAndSetDifficultyIfNew(difficulty); } else { process.exit("Difficulty from authorizeFn callback is neither a function or a number"); } @@ -140,7 +147,6 @@ var StratumClient = function(options){ }); return; } - console.log("SUBMIT "+JSON.stringify(message)); _this.emit('submit', { name : message.params[0], @@ -164,7 +170,6 @@ var StratumClient = function(options){ for (var i = 0; i < arguments.length; i++){ response += JSON.stringify(arguments[i]) + '\n'; } - console.log('response: ' + response); options.socket.write(response); } @@ -186,8 +191,9 @@ var StratumClient = function(options){ catch(e){ console.log('could not parse stratum client socket message: ' + message); } - if (messageJson) + if (messageJson) { handleMessage(messageJson); + } }); dataBuffer = ''; } @@ -205,14 +211,28 @@ var StratumClient = function(options){ //public members - this.sendDifficulty = function(difficulty){ - _this.difficulty = difficulty; - console.log("SENDING DIFFICULTY "+difficulty); - sendJson({ - id : null, - method: "mining.set_difficulty", - params: [difficulty]//[512], - }); + /** + * 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){ @@ -227,6 +247,13 @@ 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 @@ -247,8 +274,8 @@ var StratumServer = exports.Server = function StratumServer(options){ } ); stratumClients[subscriptionId] = client; - _this.emit('client', client); - c.on('disconnect', function() { + _this.emit('client.connected', client); + client.on('socketDisconnect', function() { delete stratumClients[subscriptionId]; _this.emit('client.disconnected', client); }); @@ -261,9 +288,13 @@ var StratumServer = exports.Server = function StratumServer(options){ //public members - this.broadcastMiningJobs = function(jobParams){ + this.broadcastMiningJobs = function(jobParams) { for (var clientId in stratumClients) { - stratumClients[clientId].sendMiningJob(jobParams) + // 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); + } } }; }; diff --git a/libs/transactions.js b/libs/transactions.js index e67cf88..28c1259 100644 --- a/libs/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];