From 3a9b8b94aaa4c1913413156b1700346dec411726 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Sun, 12 Jan 2014 21:11:20 +0100 Subject: [PATCH 1/6] Refactored folder structure to better match node module standard structure. Added comments on some classes --- blockNotify.js | 30 ----- {coins => example/coins}/dogecoin.json | 0 config.json => example/config.json | 0 init.js => example/init.js | 2 +- shareManager.js => example/shareManager.js | 0 pool.js => index.js | 26 ++--- blockTemplate.js => libs/blockTemplate.js | 12 +- daemon.js => libs/daemon.js | 26 +++-- jobManager.js => libs/jobManager.js | 8 +- merkleTree.js => libs/merkleTree.js | 0 stratum.js => libs/stratum.js | 41 +++---- transactions.js => libs/transactions.js | 0 util.js => libs/util.js | 0 scripts/blockNotify.js | 38 +++++++ test.js | 121 --------------------- 15 files changed, 103 insertions(+), 201 deletions(-) delete mode 100644 blockNotify.js rename {coins => example/coins}/dogecoin.json (100%) rename config.json => example/config.json (100%) rename init.js => example/init.js (95%) rename shareManager.js => example/shareManager.js (100%) rename pool.js => index.js (90%) rename blockTemplate.js => libs/blockTemplate.js (90%) rename daemon.js => libs/daemon.js (74%) rename jobManager.js => libs/jobManager.js (91%) rename merkleTree.js => libs/merkleTree.js (100%) rename stratum.js => libs/stratum.js (84%) rename transactions.js => libs/transactions.js (100%) rename util.js => libs/util.js (100%) create mode 100644 scripts/blockNotify.js delete mode 100644 test.js 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 100% rename from coins/dogecoin.json rename to example/coins/dogecoin.json 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 95% rename from init.js rename to example/init.js index 7a7f9b7..8f37aba 100644 --- a/init.js +++ b/example/init.js @@ -2,7 +2,7 @@ var net = require('net'); var fs = require('fs'); var path = require('path'); -var pool = require('./pool.js'); +var pool = require('../index.js'); var ShareManager = require('./shareManager.js').ShareManager; var logRef = console.log; diff --git a/shareManager.js b/example/shareManager.js similarity index 100% rename from shareManager.js rename to example/shareManager.js diff --git a/pool.js b/index.js similarity index 90% rename from pool.js rename to index.js index f9e0b9a..4708e0f 100644 --- a/pool.js +++ b/index.js @@ -1,14 +1,17 @@ -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'); +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'); +/** + * 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){ var _this = this; @@ -27,9 +30,6 @@ var pool = module.exports = function pool(coin){ } }).on('blockFound', function(blockHex, headerHex, third){ - console.log("BLOCK "+blockHex); - console.log("HEADER "+headerHex); - console.log("THIRD "+third); if (coin.options.hasSubmitMethod) { _this.daemon.cmd('submitblock', [blockHex], 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 91% rename from jobManager.js rename to libs/jobManager.js index 697c2c5..7cce92c 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 @@ -165,7 +169,7 @@ var JobManager = module.exports = function JobManager(options){ if (job.target.ge(headerBigNum)){ var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); - _this.emit('blockFound', blockBuf.toString('hex'), headerBigNum.toString(16), coinbaseHash.toString('hex')); + _this.emit('blockFound', blockBuf.toString('hex')); } 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/stratum.js b/libs/stratum.js similarity index 84% rename from stratum.js rename to libs/stratum.js index 2486600..076a4da 100644 --- a/stratum.js +++ b/libs/stratum.js @@ -18,6 +18,14 @@ var SubscriptionCounter = function(){ }; }; + +/** + * Defining each client that connects to the stratum server. + * Emits: + * - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size)) + * - 'authorize'() FIX THIS + * - 'submit' FIX THIS. +**/ var StratumClient = function(options){ //private members @@ -79,16 +87,11 @@ var StratumClient = function(options){ }, 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, + id : message.id, result: result, - error: error + error : error }); } ); @@ -97,34 +100,34 @@ var StratumClient = function(options){ function handleSubmit(message){ if (!_this.authorized){ sendJson({ - id: message.id, + id : message.id, result: null, - error: [24, "unauthorized worker", null] + error : [24, "unauthorized worker", null] }); return; } if (!_this.extraNonce1){ sendJson({ - id: message.id, + id : message.id, result: null, - error: [25, "not subscribed", null] + error : [25, "not subscribed", null] }); return; } console.log("SUBMIT "+JSON.stringify(message)); _this.emit('submit', { - name: message.params[0], - jobId: message.params[1], + name : message.params[0], + jobId : message.params[1], extraNonce2: message.params[2], - nTime: message.params[3], - nonce: message.params[4] + nTime : message.params[3], + nonce : message.params[4] }, function(error, result){ sendJson({ - id: message.id, + id : message.id, result: result, - error: error + error : error }); } ); @@ -179,14 +182,14 @@ var StratumClient = function(options){ this.sendDifficulty = function(difficulty){ _this.difficulty = difficulty; sendJson({ - id: null, + id : null, method: "mining.set_difficulty", params: [difficulty]//[512], }); }; this.sendMiningJob = function(jobParams){ sendJson({ - id: null, + id : null, method: "mining.notify", params: jobParams }); diff --git a/transactions.js b/libs/transactions.js similarity index 100% rename from transactions.js rename to libs/transactions.js 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/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 From 6472baacc2e745e10ab2cebfa3edad5c7e0b07dc Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Sun, 12 Jan 2014 21:54:20 +0100 Subject: [PATCH 2/6] Removed 'authorize' emit event and created which is now replaced by an authorize function. Added ip address to the authorize event Added ability for the authorizeFn to ask StratumClient to close connection --- index.js | 38 +++++++++++++++++++++++++----------- libs/stratum.js | 52 +++++++++++++++++++++++++++---------------------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/index.js b/index.js index 4708e0f..3890b71 100644 --- a/index.js +++ b/index.js @@ -22,6 +22,23 @@ var pool = module.exports = function pool(coin){ algorithm: coin.options.algorithm, address: coin.options.address }); + + + // Worker authorizer fn. + var authorizeFn; + if ( typeof (coin.authorizeFn) === 'function' ) { + authorizeFn = coin.authorizeFn; + } else { + authorizeFn = function (ip, workerName, password, cback) { + // Default implementation just returns true + console.log("Athorize ["+ip+"] "+workerName+":"+password); + cback(null, true, true); + }; + } + + + + this.jobManager.on('newBlock', function(blockTemplate){ if ( typeof(_this.stratumServer ) === 'undefined') { console.warn("Stratum server still not started! cannot broadcast block!"); @@ -108,7 +125,8 @@ var pool = module.exports = function pool(coin){ 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(){ _this.emit('started'); @@ -127,8 +145,6 @@ var pool = module.exports = function pool(coin){ } else { this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); } - }).on('authorize', function(params, resultCallback){ - resultCallback(null, true); }).on('submit', function(params, resultCallback){ var result =_this.jobManager.processShare( params.jobId, @@ -147,14 +163,14 @@ var pool = module.exports = function pool(coin){ } 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 + 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/stratum.js b/libs/stratum.js index 076a4da..7cb932f 100644 --- a/libs/stratum.js +++ b/libs/stratum.js @@ -23,7 +23,6 @@ var SubscriptionCounter = function(){ * Defining each client that connects to the stratum server. * Emits: * - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size)) - * - 'authorize'() FIX THIS * - 'submit' FIX THIS. **/ var StratumClient = function(options){ @@ -80,21 +79,21 @@ var StratumClient = function(options){ } function handleAuthorize(message){ - _this.emit('authorize', - { - name: message.params[0][0], - password: message.params[0][1] - }, - function(error, result){ - _this.authorized = result; - - sendJson({ - id : message.id, - result: result, - error : error + var workerName = message.params[0]; + var workerPass = message.params[1]; + options.authorizeFn(options.socket.address().address, workerName, workerPass, function(err, authorized, shouldCloseSocket) { + _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(); } - ); + }); } function handleSubmit(message){ @@ -117,17 +116,17 @@ var StratumClient = function(options){ 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] + 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 + id : message.id, + result : result, + error : error }); } ); @@ -187,6 +186,7 @@ var StratumClient = function(options){ params: [difficulty]//[512], }); }; + this.sendMiningJob = function(jobParams){ sendJson({ id : null, @@ -211,7 +211,13 @@ var StratumServer = exports.Server = function StratumServer(options){ (function init(){ _socketServer = socketServer = net.createServer(function(c){ var subscriptionId = subscriptionCounter.next(); - var client = new StratumClient({subscriptionId: subscriptionId, socket: c}); + var client = new StratumClient( + { + subscriptionId : subscriptionId, + socket : c, + authorizeFn : options.authorizeFn + } + ); stratumClients[subscriptionId] = client; _this.emit('client', client); }); From 475dab8aab1033e4b98c4b8774f8425874c14852 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Sun, 12 Jan 2014 23:43:02 +0100 Subject: [PATCH 3/6] Added difficulty parameter to the authorizeFn --- example/init.js | 14 +++++++++++++- index.js | 29 ++++++++++++++--------------- libs/stratum.js | 34 +++++++++++++++++++++++++++++++--- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/example/init.js b/example/init.js index 8f37aba..334c066 100644 --- a/example/init.js +++ b/example/init.js @@ -24,6 +24,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,7 +47,7 @@ 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.pool = new pool(coin, authorizeFN ); coin.shareManager = new ShareManager(coin.pool); coins.push(coin); diff --git a/index.js b/index.js index 3890b71..1253474 100644 --- a/index.js +++ b/index.js @@ -12,7 +12,7 @@ var util = require('./libs/util.js'); * - '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){ +var pool = module.exports = function pool(coin, authFn){ var _this = this; var publicKeyBuffer; @@ -23,17 +23,15 @@ var pool = module.exports = function pool(coin){ address: coin.options.address }); - + + // Worker authorizer fn. var authorizeFn; - if ( typeof (coin.authorizeFn) === 'function' ) { - authorizeFn = coin.authorizeFn; + if ( typeof (authFn) === 'function' ) { + authorizeFn = authFn; } else { - authorizeFn = function (ip, workerName, password, cback) { - // Default implementation just returns true - console.log("Athorize ["+ip+"] "+workerName+":"+password); - cback(null, true, true); - }; + // probably undefined. + process.exit("ERROR: an authorize Function is needed.") } @@ -133,18 +131,19 @@ var pool = module.exports = function pool(coin){ console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name); }).on('client', 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()); - } + var clientThis = this; + + //if (clientThis.authorized) { + clientThis.sendMiningJob(_this.jobManager.currentJob.getJobParams()); + //} + }).on('submit', function(params, resultCallback){ var result =_this.jobManager.processShare( params.jobId, diff --git a/libs/stratum.js b/libs/stratum.js index 7cb932f..5eeeab6 100644 --- a/libs/stratum.js +++ b/libs/stratum.js @@ -53,6 +53,9 @@ var StratumClient = function(options){ } function handleSubscribe(message){ + if (! _this._authorized ) { + _this.requestedSubscriptionBeforeAuth = true; + } _this.emit('subscription', {}, function(error, extraNonce1, extraNonce2Size){ @@ -79,9 +82,10 @@ var StratumClient = function(options){ } function handleAuthorize(message){ - var workerName = message.params[0]; - var workerPass = message.params[1]; - options.authorizeFn(options.socket.address().address, workerName, workerPass, function(err, authorized, shouldCloseSocket) { + 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, @@ -93,6 +97,29 @@ var StratumClient = function(options){ 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.sendDifficulty(diff); + } + + }); + } else if (typeof(difficulty) === 'number') { + _this.sendDifficulty(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..... + } + }); } @@ -180,6 +207,7 @@ var StratumClient = function(options){ this.sendDifficulty = function(difficulty){ _this.difficulty = difficulty; + console.log("SENDING DIFFICULTY "+difficulty); sendJson({ id : null, method: "mining.set_difficulty", From 263972fcdce2407694827373e0fb2096031d1051 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Mon, 13 Jan 2014 11:06:43 +0100 Subject: [PATCH 4/6] When clients disconnect new emit + memory clean up Replaced some console.log with console.error --- example/coins/dogecoin.json | 2 +- index.js | 11 +++++------ libs/stratum.js | 6 +++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/example/coins/dogecoin.json b/example/coins/dogecoin.json index eb87631..2af1290 100644 --- a/example/coins/dogecoin.json +++ b/example/coins/dogecoin.json @@ -10,6 +10,6 @@ "host": "localhost", "port": 19334, "user": "testnet", - "password": "AHhQYqfSZqzQvkSXAtHtDAbKaZaoPih3wfmJfgCtjRx9" + "password": "testnet1" } } diff --git a/index.js b/index.js index 1253474..111009c 100644 --- a/index.js +++ b/index.js @@ -76,15 +76,14 @@ var pool = module.exports = function pool(coin, authFn){ [coin.options.address], function(error, result){ if (error){ - console.log('validateaddress rpc error for ' + coin.options.name); + console.error('validateaddress rpc error for ' + coin.options.name); callback(error); - } - else if (!result.isvalid){ - console.log('address is not valid for ' + coin.options.name); + } else if (!result.isvalid) { + console.error('address is not valid for ' + coin.options.name); callback(error); - } - else + } else { callback(error, result); + } } ); }, diff --git a/libs/stratum.js b/libs/stratum.js index 5eeeab6..a8e3f7b 100644 --- a/libs/stratum.js +++ b/libs/stratum.js @@ -248,6 +248,10 @@ var StratumServer = exports.Server = function StratumServer(options){ ); stratumClients[subscriptionId] = client; _this.emit('client', client); + c.on('disconnect', function() { + delete stratumClients[subscriptionId]; + _this.emit('client.disconnected', client); + }); }); _socketServer.listen(options.port, function(){ _this.emit('started'); @@ -258,7 +262,7 @@ var StratumServer = exports.Server = function StratumServer(options){ //public members this.broadcastMiningJobs = function(jobParams){ - for (var clientId in stratumClients){ + for (var clientId in stratumClients) { stratumClients[clientId].sendMiningJob(jobParams) } }; From a42fe87723b1644b71f6beff601c623c3305c7b4 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Mon, 13 Jan 2014 16:17:38 +0100 Subject: [PATCH 5/6] 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]; From fc8e18645b82ede7dfdd2eb3d3567a334441484f Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Mon, 13 Jan 2014 17:54:35 +0100 Subject: [PATCH 6/6] Fixed Todolist markup --- TODOLIST.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TODOLIST.md b/TODOLIST.md index 264693f..97523d8 100644 --- a/TODOLIST.md +++ b/TODOLIST.md @@ -1,11 +1,11 @@ -=== TODO === +#### 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 === +#### 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 +#### DONE \ No newline at end of file