From ffc01054c6b7a995eccf59df962209adfd0b6c7e Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 23 Mar 2014 22:22:01 -0600 Subject: [PATCH] Added fixes for POS. And when getblocktemplate fails because its out of sync then it shows syncing progress :) --- README.md | 1 - lib/daemon.js | 7 +- lib/jobManager.js | 13 ++- lib/pool.js | 218 ++++++++++++++++++++++++++++++---------------- 4 files changed, 156 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 61b669e..85221f2 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ var pool = Stratum.createPool({ name: "Dogecoin", symbol: "doge", algorithm: "scrypt", //or "sha256", "scrypt-jane", "quark", "x11" - reward: "POW", //or "POS" txMessages: false //or true }, diff --git a/lib/daemon.js b/lib/daemon.js index 9bad748..9646aa4 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -67,6 +67,11 @@ function DaemonInterface(options){ var dataJson; var parsingError; + + //if (data.indexOf(':-nan,') !== -1){ + // data = data.replace(/:-nan,/g, ":0,") + //} + try{ dataJson = JSON.parse(data); @@ -79,7 +84,7 @@ function DaemonInterface(options){ else{ parsingError = e; _this.emit('error', 'could not parse rpc data with request of: ' + jsonData + - ' on instance ' + instance.index + ' data: ' + data); + ' on instance ' + instance.index + ' data: ' + data + ' Error ' + JSON.stringify(parsingError)); } } if (typeof(dataJson) !== 'undefined'){ diff --git a/lib/jobManager.js b/lib/jobManager.js index af0d73b..407fc0a 100644 --- a/lib/jobManager.js +++ b/lib/jobManager.js @@ -50,6 +50,7 @@ var JobCounter = function(){ **/ var JobManager = module.exports = function JobManager(options){ + //private members var _this = this; @@ -58,7 +59,7 @@ var JobManager = module.exports = function JobManager(options){ //Which number to use as dividend when converting difficulty to target var diffDividend = bignum((function(){ - switch(options.algorithm){ + switch(options.coin.algorithm){ case 'sha256': case 'skein': return '00000000ffff0000000000000000000000000000000000000000000000000000'; @@ -76,7 +77,7 @@ var JobManager = module.exports = function JobManager(options){ //On initialization lets figure out which hashing algorithm to use var hashDigest = (function(){ - switch(options.algorithm){ + switch(options.coin.algorithm){ case 'sha256': return function(){ return util.doublesha.apply(this, arguments); @@ -119,6 +120,10 @@ var JobManager = module.exports = function JobManager(options){ //https://github.com/Prydie/maxcoin-hash-python //https://github.com/ahmedbodi/stratum-mining-maxcoin/blob/master/lib/template_registry.py } + default: + return function(){ + console.log('Hashing algorithm ' + options.coin.algorithm + ' not supported'); + } } })(); @@ -167,8 +172,8 @@ var JobManager = module.exports = function JobManager(options){ rpcData, publicKey, _this.extraNoncePlaceholder, - options.reward, - options.txMessages + options.coin.reward, + options.coin.txMessages ); this.currentJob = tmpBlockTemplate; diff --git a/lib/pool.js b/lib/pool.js index b169640..d6ba734 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -39,14 +39,7 @@ var pool = module.exports = function pool(options, authorizeFn){ emitLog('Starting pool for ' + options.coin.name + ' [' + options.coin.symbol.toUpperCase() + ']'); SetupJobManager(); SetupVarDiff(); - SetupDaemonInterface(function (err, newDaemon) { - if (!err) { - _this.daemon = newDaemon; - SetupBlockPolling(); - StartStratumServer(); - SetupPeer(); - } - }); + SetupDaemonInterface(); SetupApi(); }; @@ -76,6 +69,7 @@ var pool = module.exports = function pool(options, authorizeFn){ } function SetupVarDiff(){ + _this.varDiff = {}; Object.keys(options.ports).forEach(function(port) { if (options.ports[port].varDiff) _this.setVarDiff(port, new varDiff(port, options.ports[port].varDiff)); @@ -120,13 +114,9 @@ var pool = module.exports = function pool(options, authorizeFn){ function SetupJobManager(){ - _this.jobManager = new jobManager({ - address : options.address, - algorithm : options.coin.algorithm, - reward : options.coin.reward, - txMessages: options.coin.txMessages, - txRefreshInterval: options.txRefreshInterval - }); + + _this.jobManager = new jobManager(options); + _this.jobManager.on('newBlock', function(blockTemplate){ //Check if stratumServer has been initialized yet if ( typeof(_this.stratumServer ) !== 'undefined') { @@ -170,12 +160,15 @@ var pool = module.exports = function pool(options, authorizeFn){ } - function SetupDaemonInterface(cback){ - var newDaemon = new daemon.interface(options.daemons); - newDaemon.once('online', function(){ + function SetupDaemonInterface(){ + + if (!_this.daemon) _this.daemon = new daemon.interface(options.daemons); + + _this.daemon.once('online', function(){ async.parallel({ + addressInfo: function(callback){ - newDaemon.cmd('validateaddress', [options.address], function(results){ + _this.daemon.cmd('validateaddress', [options.address], function(results){ //Make sure address is valid with each daemon var allValid = results.every(function(result){ @@ -197,8 +190,9 @@ var pool = module.exports = function pool(options, authorizeFn){ }); }, - miningInfo: function(callback){ - newDaemon.cmd('getmininginfo', [], function(results){ + + info: function(callback){ + _this.daemon.cmd('getinfo', [], function(results){ // Print which network each daemon is running on @@ -212,9 +206,6 @@ var pool = module.exports = function pool(options, authorizeFn){ return false; } - var network = result.response.testnet ? 'testnet' : 'live blockchain'; - emitLog('Daemon instance ' + result.instance.index + ' is running on ' + network); - if (typeof isTestnet === 'undefined'){ isTestnet = result.response.testnet; return true; @@ -242,17 +233,41 @@ var pool = module.exports = function pool(options, authorizeFn){ }); }, + + rewardType: function(callback){ + _this.daemon.cmd('getdifficulty', [], function(results){ + + var isPos = results.every(function(result){ + + if (result.error){ + emitErrorLog('getinfo on init failed with daemon instance ' + + result.instance.index + ', error ' + JSON.stringify(result.error) + ); + return false; + } + + return isNaN(result.response) && 'proof-of-stake' in result.response; + }); + + options.coin.reward = isPos ? 'POS' : 'POW' + callback(null); + + }); + }, + submitMethod: function(callback){ /* This checks to see whether the daemon uses submitblock or getblocktemplate for submitting new blocks */ - newDaemon.cmd('submitblock', [], function(results){ + _this.daemon.cmd('submitblock', [], function(results){ var couldNotDetectMethod = results.every(function(result){ if (result.error && result.error.message === 'Method not found'){ - callback(null, false); + options.hasSubmitMethod = false; + callback(null); return false; } else if (result.error && result.error.code === -1){ - callback(null, true); + options.hasSubmitMethod = true; + callback(null); return false; } else @@ -267,76 +282,127 @@ var pool = module.exports = function pool(options, authorizeFn){ }, function(err, results){ if (err){ emitErrorLog('Could not start pool, ' + JSON.stringify(err)); - cback(err); return; } - emitLog('Connected to daemon via RPC'); - - - options.hasSubmitMethod = results.submitMethod; - if (options.coin.reward === 'POS' && typeof(results.addressInfo.pubkey) == 'undefined') { // address provided is not of the wallet. emitErrorLog('The address provided is not from the daemon wallet.'); - cback(err); return; - } else { - - publicKeyBuffer = options.coin.reward === 'POW' ? - util.script_to_address(results.addressInfo.address) : - util.script_to_pubkey(results.addressInfo.pubkey); - - //var networkDifficulty = Math.round(results.miningInfo.difficulty * 65536); - - - GetBlockTemplate(newDaemon, function(error, result){ - if (error) { - emitErrorLog('Error with getblocktemplate on initializing'); - cback(error); - } else { - - var networkDifficulty = _this.jobManager.currentJob.difficulty; - emitLog('Current block height at ' + results.miningInfo.blocks + - ' with block difficulty of ' + networkDifficulty); - - Object.keys(options.ports).forEach(function(port){ - var portDiff = options.ports[port].diff; - if (portDiff > networkDifficulty) - emitWarningLog('diff of ' + portDiff + ' on port ' + port + - ' was set higher than network difficulty of ' + networkDifficulty); - }); - - - cback(null, newDaemon); // finish! - } - }); } + + publicKeyBuffer = options.coin.reward === 'POW' ? + util.script_to_address(results.addressInfo.address) : + util.script_to_pubkey(results.addressInfo.pubkey); + + var networkDifficulty = Math.round(results.info.difficulty * 65536); + var network = results.info.testnet ? 'testnet' : 'live blockchain'; + + emitLog('Connected to ' + network + + '; detected ' + options.coin.reward + + ' reward type; block height of ' + results.info.blocks + + '; difficulty of ' + networkDifficulty); + + GetBlockTemplate(function(error, result){ + if (error) { + if (error.code === -10){ + emitErrorLog('Daemon is still syncing with network (download blocks)'); + WaitForSync(results.info.blocks); + } + else + emitErrorLog('Error with getblocktemplate on initializing'); + + } else { + Object.keys(options.ports).forEach(function(port){ + var portDiff = options.ports[port].diff; + if (portDiff > networkDifficulty) + emitWarningLog('Pool difficulty of ' + portDiff + ' on port ' + port + + ' was set higher than network difficulty of ' + networkDifficulty); + }); + + SetupBlockPolling(); + StartStratumServer(); + SetupPeer(); + + } + }); + }); }).on('connectionFailed', function(error){ emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error)); + }).on('error', function(message){ emitErrorLog(message); + }); - newDaemon.init(); + _this.daemon.init(); + } + + function WaitForSync(currentBlocks){ + + var generateProgress = function(currentBlocks){ + + //get list of peers and their highest block height to compare to ours + + _this.daemon.cmd('getpeerinfo', [], function(results){ + + var peers = results[0].response; + var totalBlocks = peers.sort(function(a, b){ + return b.startingheight - a.startingheight; + })[0].startingheight; + + var percent = (currentBlocks / totalBlocks * 100).toFixed(2); + + //Only let the first fork show synced status or the log wil look flooded with it + if (process.env.forkId === '0') + emitWarningLog('Downloaded ' + percent + '% of blockchain from network'); + }); + }; + generateProgress(currentBlocks); + + setInterval(function(){ + + _this.daemon.cmd('getblocktemplate', [], function(results){ + var synced = results.every(function(r){ + return r.error.code !== -10; + }); + if (synced){ + SetupDaemonInterface(); + } + else{ + _this.daemon.cmd('getinfo', [], function(results){ + var smallestHeight = results.sort(function(a, b){ + return b.response.blocks - a.response.blocks; + })[0].response.blocks; + generateProgress(smallestHeight); + }); + } + + }); + + }, 5000); + } function StartStratumServer(){ _this.stratumServer = new stratum.Server(options.ports, options.connectionTimeout, options.banning, authorizeFn); + _this.stratumServer.on('started', function(){ emitLog('Stratum server started on port(s): ' + Object.keys(options.ports).join(', ')); _this.emit('started'); + }).on('client.connected', function(client){ if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') { _this.varDiff[client.socket.localPort].manageClient(client); } - client.on('difficultyChanged', function(diff){ + _this.emit('difficultyUpdate', client.workerName, diff); + }).on('subscription', function(params, resultCallback){ var extraNonce = _this.jobManager.extraNonceCounter.next(); @@ -370,14 +436,19 @@ var pool = module.exports = function pool(options, authorizeFn){ }).on('malformedMessage', function (message) { emitWarningLog(client.workerName + " has sent us a malformed message: " + message); + }).on('socketError', function(err) { emitWarningLog(client.workerName + " has somehow had a socket error: " + JSON.stringify(err)); + }).on('socketDisconnect', function() { emitLog("Client '" + client.workerName + "' disconnected!"); + }).on('unknownStratumMethod', function(fullMessage) { emitLog("Client '" + client.workerName + "' has sent us an unknown stratum method: " + fullMessage.method); + }).on('socketFlooded', function(){ emitWarningLog('Detected socket flooding and purged buffer'); + }).on('ban', function(ipAddress){ _this.emit('banIP', ipAddress); emitWarningLog('banned IP ' + ipAddress); @@ -400,16 +471,12 @@ var pool = module.exports = function pool(options, authorizeFn){ emitLog('Block polling every ' + pollingInterval + ' milliseconds'); } - function GetBlockTemplate(daemonObj, callback){ - if (typeof(callback) === 'undefined') { - callback = daemonObj; - daemonObj = _this.daemon; - } - daemonObj.cmd('getblocktemplate', + function GetBlockTemplate(callback){ + _this.daemon.cmd('getblocktemplate', [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], function(result){ if (result.error){ - emitErrorLog('system', 'getblocktemplate call failed for daemon instance ' + + emitErrorLog('getblocktemplate call failed for daemon instance ' + result.instance.index + ' with error ' + JSON.stringify(result.error)); callback(result.error); } else { @@ -458,7 +525,7 @@ var pool = module.exports = function pool(options, authorizeFn){ if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ GetBlockTemplate(function(error, result){ if (error) - emitErrorLog('system', 'Block notify error getting block template for ' + options.coin.name); + emitErrorLog('Block notify error getting block template for ' + options.coin.name); }) } }; @@ -508,9 +575,6 @@ var pool = module.exports = function pool(options, authorizeFn){ }; this.setVarDiff = function(port, varDiffInstance) { - if (typeof(_this.varDiff) === 'undefined') { - _this.varDiff = {}; - } if (typeof(_this.varDiff[port]) != 'undefined' ) { _this.varDiff[port].removeAllListeners(); }