From a42fe87723b1644b71f6beff601c623c3305c7b4 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Mon, 13 Jan 2014 16:17:38 +0100 Subject: [PATCH] Added todolist code cleanup Added jobManager.blockHashHex which should generate the expected block hash but it seems to not work properly Moved check for the share difficulty after the block check since it's useless to make the check there. Changed emit event from client to client.connected and added client.disconnected Changed share emit data. Now we pass the client instance to everyone listening to the share event. Added mining.get_transaction to the handled events of stratumclient. It looks like bfgminer uses this. changed StratumClient.sendDifficulty to StratumCLient.sendAndSetDifficulty which will also perform a check on the given difficulty and broadcasts the sendDifficulty message to the client Only if the new difficulty is different from the previous one. Code cleanup and comments --- TODOLIST.md | 11 ++++++ example/init.js | 13 ++++--- example/shareManager.js | 13 ++++--- index.js | 11 ++---- libs/jobManager.js | 55 +++++++++++++++++++++++------- libs/stratum.js | 75 +++++++++++++++++++++++++++++------------ libs/transactions.js | 20 +++++------ 7 files changed, 131 insertions(+), 67 deletions(-) create mode 100644 TODOLIST.md 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];