diff --git a/blockTemplate.js b/blockTemplate.js index ba2392b..8a4ffe1 100644 --- a/blockTemplate.js +++ b/blockTemplate.js @@ -6,7 +6,7 @@ var transactions = require('./transactions.js'); var util = require('./util.js'); -var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publicKey, reward, extraNoncePlaceholder){ +var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){ //private members @@ -29,7 +29,7 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ //public members this.rpcData = rpcData; - this.jobId = jobId; + this.jobId = null; this.target = util.bignumFromBits(rpcData.bits); this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex'); this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){ @@ -40,10 +40,13 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ this.generationTransaction = new transactions.Generation( rpcData, publicKey, - reward, extraNoncePlaceholder ); + this.setJobId = function (jobId) { + this.jobId = jobId; + } + this.serializeCoinbase = function(extraNonce1, extraNonce2){ return Buffer.concat([ this.generationTransaction.coinbase[0], @@ -71,7 +74,7 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ this.serializeBlock = function(header, coinbase){ return Buffer.concat([ header, - util.varIntBuffer(this.rpcData.transaction.length + 1), + util.varIntBuffer(this.rpcData.transactions.length + 1), coinbase, this.transactionData ]); diff --git a/coins/dogecoin.json b/coins/dogecoin.json index 1686fca..395991e 100644 --- a/coins/dogecoin.json +++ b/coins/dogecoin.json @@ -3,15 +3,15 @@ "symbol": "doge", "algorithm": "scrypt", "reward": "POW", - "address": "DDt79i6P3Wro3SD3HSnkRLpMgUGUGdiNhS", + "address": "mkLyYQ5U8aFQ8aDAF1GS9zXStTzi3m29Tg", "stratumPort": 3334, "difficulty": 8, "templateRefreshInterval": 60, "merkleRefreshInterval": 60, "daemon": { "host": "localhost", - "port": 8332, - "user": "test", - "password": "test" + "port": 19334, + "user": "testnet", + "password": "AHhQYqfSZqzQvkSXAtHtDAbKaZaoPih3wfmJfgCtjRx9" } -} \ No newline at end of file +} diff --git a/config.json b/config.json index 8da2b88..feffc01 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { "blockNotifyListener": { - "enabled": true, + "enabled": false, "port": 8117, "password": "test" } diff --git a/daemon.js b/daemon.js index cc243e1..1453da8 100644 --- a/daemon.js +++ b/daemon.js @@ -55,6 +55,11 @@ function DaemonInterface(options){ params: params }); + if (method == 'submitblock') { + console.log("SUBMITBLOCK daemon"); + console.log(requestJson); + } + var options = { hostname: 'localhost', port: _this.options.port, diff --git a/init.js b/init.js index 95706f3..7a7f9b7 100644 --- a/init.js +++ b/init.js @@ -3,6 +3,7 @@ var fs = require('fs'); var path = require('path'); var pool = require('./pool.js'); +var ShareManager = require('./shareManager.js').ShareManager; var logRef = console.log; console.log = function(s){ @@ -33,8 +34,28 @@ fs.readdir(confFolder, function(err, files){ var coinJson = JSON.parse(data) var coin = new Coin(coinJson); console.log('Starting pool for ' + coin.options.name); + coin.pool = new pool(coin); + coin.shareManager = new ShareManager(coin.pool); + coins.push(coin); + + // If the block notify listener is not enabled lets set up the polling. + if ( ! config.blockNotifyListener.enabled ) { + // as soon as the pool is started we start polling + var pollingTime = typeof(config.blockPollingTime) === 'undefined' ? 5000 : parseInt(config.blockPollingTime, 10); + coin.pool.on('started', function() { + var curPool = this; + setInterval( + function() { + curPool.processBlockPolling(); + }, + pollingTime + ); + }); + + + } }); }); @@ -42,6 +63,7 @@ fs.readdir(confFolder, function(err, files){ if (config.blockNotifyListener.enabled){ + console.log("ENABLED"); var blockNotifyServer = net.createServer(function(c) { console.log('server connected'); var data = ''; @@ -69,4 +91,4 @@ if (config.blockNotifyListener.enabled){ }); }); blockNotifyServer.listen(config.blockNotifyListener.port, function() {}); -} +} \ No newline at end of file diff --git a/jobManager.js b/jobManager.js index 266e304..697c2c5 100644 --- a/jobManager.js +++ b/jobManager.js @@ -36,6 +36,10 @@ var JobCounter = function(){ counter++; if (counter % 0xffff === 0) counter = 1; + return this.cur(); + }; + + this.cur = function () { return counter.toString(16); }; }; @@ -50,15 +54,19 @@ var JobManager = module.exports = function JobManager(options){ var jobs = {}; - + /** + * It only checks if the blockTemplate is already in our jobs list. + * @returns true if it's a new block, false otherwise. + * used by onNewTemplate + **/ function CheckNewIfNewBlock(blockTemplate){ var newBlock = true; for(var job in jobs){ - if (jobs[job].rpcData.previousblockhash === blockTemplate.rpcData.previousblockhash) + if (jobs[job].rpcData.previousblockhash === blockTemplate.rpcData.previousblockhash) { newBlock = false; + } } - if (newBlock) - _this.emit('newBlock', blockTemplate); + return newBlock; } var diffDividend = (function(){ @@ -102,9 +110,14 @@ var JobManager = module.exports = function JobManager(options){ this.currentJob; this.newTemplate = function(rpcData, publicKey){ - this.currentJob = new blockTemplate(jobCounter.next(), rpcData, publicKey, _this.extraNoncePlaceholder); - jobs[this.currentJob.jobId] = this.currentJob; - CheckNewIfNewBlock(this.currentJob); + var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder); + if ( CheckNewIfNewBlock(tmpBlockTemplate) ) { + tmpBlockTemplate.setJobId(jobCounter.next()); + jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate; + this.currentJob = jobs[tmpBlockTemplate.jobId]; + _this.emit('newBlock', tmpBlockTemplate); + } + }; this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){ @@ -144,17 +157,18 @@ var JobManager = module.exports = function JobManager(options){ var headerHash = hashDigest(headerBuffer, nTimeInt); var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32}); - if (job.target.ge(headerBigNum)){ - var blockHex = job.serializeBlock(headerBuffer, coinbaseBuffer); - _this.emit('blockFound', blockHex); - } - var targetUser = bignum(diffDividend / difficulty); if (headerBigNum.gt(targetUser)){ return {error: [23, 'low difficulty share', null]}; } - return {result: true}; + if (job.target.ge(headerBigNum)){ + var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); + + _this.emit('blockFound', blockBuf.toString('hex'), headerBigNum.toString(16), coinbaseHash.toString('hex')); + } + + return {result: true, headerHEX: headerBigNum.toString(16)}; }; }; JobManager.prototype.__proto__ = events.EventEmitter.prototype; \ No newline at end of file diff --git a/pool.js b/pool.js index 939fc41..3c5803a 100644 --- a/pool.js +++ b/pool.js @@ -9,35 +9,46 @@ var stratum = require('./stratum.js'); var jobManager = require('./jobManager.js'); var util = require('./util.js'); - - var pool = module.exports = function pool(coin){ 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 }); this.jobManager.on('newBlock', function(blockTemplate){ - _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); - }).on('blockFound', function(blockHex){ - - if (coin.options.hasSubmitMethod) + if ( typeof(_this.stratumServer ) === 'undefined') { + console.warn("Stratum server still not started! cannot broadcast block!"); + } else { + _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); + } + + }).on('blockFound', function(blockHex, headerHex, third){ + console.log("BLOCK "+blockHex); + console.log("HEADER "+headerHex); + console.log("THIRD "+third); + if (coin.options.hasSubmitMethod) { _this.daemon.cmd('submitblock', [blockHex], function(error, result){ - + console.log(JSON.stringify(error)); + console.log(JSON.stringify(result)); + console.log("submitblock", JSON.stringify(error), JSON.stringify(result)); } ); - else + } else { _this.daemon.cmd('getblocktemplate', [{'mode': 'submit', 'data': blockHex}], function(error, result){ - + console.log(JSON.stringify(error)); + console.log(JSON.stringify(result)); + console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result)); } ); + } }); console.log('Connecting to daemon for ' + coin.options.name); @@ -84,7 +95,7 @@ var pool = module.exports = function pool(coin){ _this.jobManager.newTemplate(results.rpcTemplate, publicKeyBuffer); - StartStatumServer(); + StartStratumServer(); }); @@ -93,13 +104,17 @@ var pool = module.exports = function pool(coin){ }); - function StartStatumServer(){ + 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 }); _this.stratumServer.on('started', function(){ +<<<<<<< HEAD +======= + _this.emit('started'); +>>>>>>> a301c49943980d0209fb42114999099eeeb44ded console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name); }).on('client', function(client){ client.on('subscription', function(params, resultCallback){ @@ -110,7 +125,11 @@ var pool = module.exports = function pool(coin){ extraNonce2Size ); this.sendDifficulty(coin.options.difficulty); - this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); + 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); }).on('submit', function(params, resultCallback){ @@ -124,9 +143,24 @@ var pool = module.exports = function pool(coin){ ); if (result.error){ resultCallback(result.error); - return; + _this.emit('share', false, { + workerName : params.name, + 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 + }); } - resultCallback(null, true); + }); }); } @@ -135,22 +169,37 @@ var pool = module.exports = function pool(coin){ _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(error, result){ - if (error){ - console.log('getblocktemplate rpc error for ' + coin.options.name); + if (error) { callback(error); - } - else{ + } else { callback(null, result); } } ); } + /** + * This method needs to be called to perform a block polling to the daemon so that we can notify our miners + * about new blocks + **/ + this.processBlockPolling = function() { + GetBlockTemplate(function(error, result) { + if (error) { + console.error("[processBlockPolling] Error getting block template for " + coin.options.name); + } + _this.jobManager.newTemplate(result, publicKeyBuffer); + }); + } + + /** + * This method is being called from the blockNotify so that when a new block is discovered by the daemon + * We can inform our miners about the newly found block + **/ this.processBlockNotify = function(blockHash){ if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ GetBlockTemplate(function(error, result){ if (error){ - console.log('Error getting block template for ' + coin.options.name); + console.error('[processBlockNotify] Error getting block template for ' + coin.options.name); return; } _this.jobManager.newTemplate(result, publicKeyBuffer); diff --git a/shareManager.js b/shareManager.js index e69de29..636aa39 100644 --- a/shareManager.js +++ b/shareManager.js @@ -0,0 +1,41 @@ +var events = require('events'); + +/** + * This ShareManager Events table: will emit the following events: + * + * LISTENS on: + * - pool('share') +**/ + +var ShareManager = exports.ShareManager = function(pool) { + pool.on('share', function(isValid, data) { + if (isValid) { + handleValidShare( + data.workerName, + data.blockHeaderHex, + data.jobId, + data.clientDifficulty, + data.extraNonce1, + data.extraNonce2, + data.nTime, + data.nonce); + } else { + handleInvalidShare( + data.workerName, + 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 handleInvalidShare(workerName, errorCode, errorDescription) { + console.log("Invalid share form "+workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription); + } +}; + +ShareManager.prototype.__proto__ = events.EventEmitter.prototype; + + diff --git a/stratum.js b/stratum.js index 17d5809..2486600 100644 --- a/stratum.js +++ b/stratum.js @@ -79,6 +79,12 @@ 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, result: result, @@ -105,6 +111,7 @@ var StratumClient = function(options){ }); return; } + console.log("SUBMIT "+JSON.stringify(message)); _this.emit('submit', { name: message.params[0], @@ -214,7 +221,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) } };