From e02b265d13fa7632a809e804b638651f2e00d5f3 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 15 Jan 2014 17:37:43 -0700 Subject: [PATCH] Added lots of comments, cleaned up a some code --- lib/blockTemplate.js | 28 +++++---- lib/daemon.js | 2 +- lib/index.js | 1 - lib/jobManager.js | 26 ++++---- lib/merkleTree.js | 9 --- lib/pool.js | 49 +++++++++------ lib/stratum.js | 4 +- lib/util.js | 146 +++++++++++-------------------------------- 8 files changed, 95 insertions(+), 170 deletions(-) diff --git a/lib/blockTemplate.js b/lib/blockTemplate.js index e42155d..a2b17b3 100644 --- a/lib/blockTemplate.js +++ b/lib/blockTemplate.js @@ -1,14 +1,17 @@ var binpack = require('binpack'); +var bignum = require('bignum'); + var merkleTree = require('./merkleTree.js'); var transactions = require('./transactions.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){ +var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publicKey, extraNoncePlaceholder){ //private members @@ -31,8 +34,15 @@ var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, //public members this.rpcData = rpcData; - this.jobId = null; - this.target = util.bignumFromBits(rpcData.bits); + this.jobId = jobId; + + /* + Use the 'target' field if available, but some daemons only return the 'bits' field + */ + this.target = rpcData.target ? + bignum.fromBuffer(new Buffer(rpcData.target, 'hex')) : + util.bignumFromBits(rpcData.bits); + this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex'); this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){ return new Buffer(tx.data, 'hex'); @@ -45,10 +55,6 @@ var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder ); - this.setJobId = function (jobId) { - this.jobId = jobId; - } - this.serializeCoinbase = function(extraNonce1, extraNonce2){ return Buffer.concat([ this.generationTransaction[0], @@ -106,9 +112,5 @@ var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, ]; } return this.jobParams; - } - - //this.jobParams = this.getJobParams(); - //console.log(JSON.stringify(this.jobParams, null, ' ').replace(/\n/g ,'')); - -} \ No newline at end of file + }; +}; \ No newline at end of file diff --git a/lib/daemon.js b/lib/daemon.js index 67de845..0e54f21 100644 --- a/lib/daemon.js +++ b/lib/daemon.js @@ -5,7 +5,7 @@ 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 + * in order 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 diff --git a/lib/index.js b/lib/index.js index bba5976..924020b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,4 @@ var net = require('net'); -var fs = require('fs'); var events = require('events'); var pool = require('./pool.js'); diff --git a/lib/jobManager.js b/lib/jobManager.js index 726ad01..1b25b5a 100644 --- a/lib/jobManager.js +++ b/lib/jobManager.js @@ -116,13 +116,9 @@ var JobManager = module.exports = function JobManager(options){ this.processTemplate = function(rpcData, publicKey){ if (CheckNewIfNewBlock(rpcData.previousblockhash)){ - var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder); - tmpBlockTemplate.setJobId(jobCounter.next()); - - this.currentJob = tmpBlockTemplate; - + var tmpBlockTemplate = new blockTemplate(jobCounter.next(), rpcData, publicKey, _this.extraNoncePlaceholder); + this.currentJob = tmpBlockTemplate; _this.emit('newBlock', tmpBlockTemplate); - } }; @@ -135,36 +131,36 @@ var JobManager = module.exports = function JobManager(options){ ip: ipAddress, worker: workerName, difficulty: difficulty, - error: error.error[1] + error: error[1] }); - return error; + return {error: error, result: null}; }; var submitTime = Date.now() / 1000 | 0; if (extraNonce2.length / 2 !== _this.extraNonce2Size) - return shareError({error: [20, 'incorrect size of extranonce2']}); + return shareError([20, 'incorrect size of extranonce2']); var job = this.currentJob; if ( job.jobId != jobId ) { - return shareError({error: [21, 'job not found']}); + return shareError([21, 'job not found']); } if (nTime.length !== 8) { - return shareError({error: [20, 'incorrect size of ntime']}); + return shareError([20, 'incorrect size of ntime']); } var nTimeInt = parseInt(nTime, 16); if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) { - return shareError({error: [20, 'ntime out of range']}); + return shareError([20, 'ntime out of range']); } if (nonce.length !== 8) { - return shareError({error: [20, 'incorrect size of nonce']}); + return shareError([20, 'incorrect size of nonce']); } if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) { - return shareError({error: [22, 'duplicate share']}); + return shareError([22, 'duplicate share']); } @@ -190,7 +186,7 @@ var JobManager = module.exports = function JobManager(options){ else { var targetUser = bignum(diffDividend / difficulty); if (headerBigNum.gt(targetUser)){ - return shareError({error: [23, 'low difficulty share']}); + return shareError([23, 'low difficulty share']); } } diff --git a/lib/merkleTree.js b/lib/merkleTree.js index 95fb0c4..6090517 100644 --- a/lib/merkleTree.js +++ b/lib/merkleTree.js @@ -49,19 +49,10 @@ var MerkleTree = module.exports = function MerkleTree(data){ } MerkleTree.prototype = { - - hashSteps: function(){ - if (!this.stepsHash) - this.stepsHash = util.doublesha(Buffer.concat(this.steps)); - return this.stepsHash; - }, withFirst: function(f){ this.steps.forEach(function(s){ f = util.doublesha(Buffer.concat([f, s])); }); return f; - }, - merkleRoot: function(){ - return this.withFirst(this.data[0]); } }; \ No newline at end of file diff --git a/lib/pool.js b/lib/pool.js index ee95b44..d604070 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -1,17 +1,24 @@ -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'); + /** * 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) + * - started() - when the pool is effectively started + * - share(isValidShare, isValidBlock, shareData) - When a share is submitted + * - log(severity, key, text) - for debug, warning, and error messages + * + * It initializes and connects: + * - JobManager - for generating miner work, processing block templates and shares + * - DaemonInterface - for RPC communication with daemon + * - StratumServer - for TCP socket communication with miners + * */ + var pool = module.exports = function pool(options, authorizeFn){ this.options = options; @@ -29,6 +36,9 @@ var pool = module.exports = function pool(options, authorizeFn){ })(); + /* + Coin daemons either use submitblock or getblocktemplate for submitting new blocks + */ function SubmitBlock(blockHex, callback){ if (options.hasSubmitMethod) { _this.daemon.cmd('submitblock', @@ -62,9 +72,8 @@ var pool = module.exports = function pool(options, authorizeFn){ address : options.address }); _this.jobManager.on('newBlock', function(blockTemplate){ - if ( typeof(_this.stratumServer ) === 'undefined') { - emitWarningLog("Stratum server still not started! cannot broadcast block!"); - } else { + //Check if stratumServer has been initialized yet + if ( typeof(_this.stratumServer ) !== 'undefined') { emitLog('system', 'Detected new block'); _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); } @@ -116,6 +125,10 @@ var pool = module.exports = function pool(options, authorizeFn){ ); }, submitMethod: function(callback){ + /* + This checks to see whether the daemon uses submitblock + or getblocktemplate for submitting new blocks + */ _this.daemon.cmd('submitblock', [], function(error, result){ @@ -136,9 +149,15 @@ var pool = module.exports = function pool(options, authorizeFn){ util.script_to_address(results.addressInfo.address) : util.script_to_pubkey(results.addressInfo.pubkey); - StartStratumServer(); - SetupBlockPolling(); + GetBlockTemplate(function(error, result){ + if (error) + emitErrorLog('system', 'Error with initial getblocktemplate'); + else{ + StartStratumServer(); + SetupBlockPolling(); + } + }); }); }).on('startFailed', function(){ @@ -157,6 +176,7 @@ var pool = module.exports = function pool(options, authorizeFn){ }); _this.stratumServer.on('started', function(){ emitLog('system','Stratum server started on port ' + options.stratumPort); + _this.emit('started'); }).on('client.connected', function(client){ client.on('subscription', function(params, resultCallback){ @@ -167,13 +187,8 @@ var pool = module.exports = function pool(options, authorizeFn){ extraNonce2Size ); - this.sendAndSetDifficultyIfNew(options.difficulty); - if (typeof(_this.jobManager.currentJob) !== 'undefined') { - this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); - } else { - emitWarningLog('client', "A miner subscribed but no job to dispatch!"); - } - + this.sendDifficulty(options.difficulty); + this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); }).on('submit', function(params, resultCallback){ var result =_this.jobManager.processShare( @@ -209,8 +224,6 @@ var pool = module.exports = function pool(options, authorizeFn){ } var pollingInterval = options.blockRefreshInterval * 1000; - var pollTimeout; - var setPoll; setInterval(function () { GetBlockTemplate(function(error, result) { diff --git a/lib/stratum.js b/lib/stratum.js index 3b12ec5..403ffc3 100644 --- a/lib/stratum.js +++ b/lib/stratum.js @@ -190,9 +190,9 @@ var StratumClient = function(options){ * IF the given difficulty is valid and new it'll send it to the client. * returns boolean **/ - this.sendAndSetDifficultyIfNew = function(difficulty){ + this.sendDifficulty = function(difficulty){ if (typeof(difficulty) != 'number') { - console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']'); + console.error('[StratumClient.sendDifficulty] given difficulty parameter is not a number: ['+difficulty+']'); return false; } diff --git a/lib/util.js b/lib/util.js index 46d7d70..9bd06aa 100644 --- a/lib/util.js +++ b/lib/util.js @@ -5,6 +5,10 @@ var base58 = require('base58-native'); var bignum = require('bignum'); +/* +Used to convert getblocktemplate bits field into target if target is not included. +More info: https://en.bitcoin.it/wiki/Target + */ exports.bignumFromBits = function(bitsString){ var bitsBuff = new Buffer(bitsString, 'hex'); var numBytes = bitsBuff.readUInt8(0); @@ -19,10 +23,6 @@ exports.bignumFromBits = function(bitsString){ return target; }; -exports.bignumFromTarget = function(targetString){ - return bignum.fromBuffer(new Buffer(targetString, 'hex')); -}; - exports.doublesha = function(buffer){ var hash1 = crypto.createHash('sha256'); hash1.update(buffer); @@ -69,6 +69,11 @@ exports.hexFromReversedBuffer = function(buffer){ return exports.reverseBuffer(buffer).toString('hex'); }; + +/* +Defined in bitcoin protocol here: + https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer + */ exports.varIntBuffer = function(n){ if (n < 0xfd) return new Buffer([n]); @@ -92,6 +97,13 @@ exports.varIntBuffer = function(n){ } }; + +/* +"serialized CScript" formatting as defined here: + https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki#specification +Used to format height and date when putting into script signature: + https://en.bitcoin.it/wiki/Script + */ exports.serializeNumber = function(n){ if (n < 0xfd){ var buff = new Buffer(2); @@ -116,6 +128,10 @@ exports.serializeNumber = function(n){ } }; + +/* +Used for serializing strings used in script signature + */ exports.serializeString = function(s){ if (s.length < 253) @@ -143,6 +159,11 @@ exports.serializeString = function(s){ ]); }; + +/* +An exact copy of python's range feature. Written by Tadeck: + http://stackoverflow.com/a/8273091 + */ exports.range = function(start, stop, step){ if (typeof stop === 'undefined'){ stop = start; @@ -161,6 +182,7 @@ exports.range = function(start, stop, step){ return result; }; + exports.address_to_pubkeyhash = function(addr){ addr = base58.decode(addr); @@ -182,6 +204,10 @@ exports.address_to_pubkeyhash = function(addr){ return [ver, addr.slice(1,-4)]; }; + +/* + For POS coins - used to format wallet address for use in generation transaction's output + */ exports.script_to_pubkey = function(key){ if (key.length === 66) key = new Buffer(key, 'hex'); if (key !== 33) throw 'Invalid address'; @@ -192,6 +218,10 @@ exports.script_to_pubkey = function(key){ return pubkey; }; + +/* +For POW coins - used to format wallet address for use in generation transaction's output + */ exports.script_to_address = function(addr){ var d = exports.address_to_pubkeyhash(addr) if (!d) @@ -200,110 +230,4 @@ exports.script_to_address = function(addr){ var ver = d[0]; var pubkeyhash = d[1]; return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkeyhash, new Buffer([0x88, 0xac])]); -}; - - -/* -exports.makeBufferReadable = function(buffer){ - var position = 0; - buffer.read = function(length){ - var section = buffer.slice(position, length ? (position + length) : buffer.length); - position += length; - return MakeBufferReadable(section); - } - return buffer; -}; - - -exports.ser_uint256 = function(u){ - var rs = new Buffer(0); - exports.range(8).forEach(function(i){ - rs = Buffer.concat([ - rs, - binpack.packUInt32(u & 0xFFFFFFFF, 'little') - ]); - u >>= 32; - }); - return rs; -}; - -exports.deser_uint256 = function(f){ - var r = 0; - exports.range(8).forEach(function(i){ - var t = f.read(4).readUInt32LE(4); - r += t << (i * 32); - }); - return r; -}; - -exports.uint256_from_compact = function(c){ - var nbytes = (c >> 24) & 0xFF; - v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) - return v; -}; - -exports.uint256_from_str = function(s){ - var r = 0; - var t = binpack.unpack -}; - -exports.ser_uint256_be = function(u){ - var rs = new Buffer(0); - exports.range(8).forEach(function(i){ - rs = Buffer.concat([ - rs, - binpack.packUInt32(u & 0xFFFFFFFF, 'big') - ]); - u >>= 32; - }); - return rs; -}; - -exports.deser_string = function(f){ - var nit = f.read(1).readUInt8(0); - if (nit == 253) - nit = f.read(2).readUInt16LE(0); - else if (nit == 254) - nit = f.read(4).readUInt32LE(1); - else if (nit == 255) - nit = f.read(8).readUInt64LE(1); - return f.read(nit); -}; - - -exports.ser_vector = function(l){ - var r; - if (l.length < 253) - r = new Buffer([l.length]); - else if (l.length < 0x10000) - r = Buffer.concat([new Buffer([253]), binpack.packUInt16(l.length, 'little')]); - else if (l.length < 0x100000000) - r = Buffer.concat([new Buffer([254]), binpack.packUInt32(l.length, 'little')]); - else - r = Buffer.concat([new Buffer([255]), binpack.packUInt64(l.length, 'little')]); - - l.forEach(function(i){ - r = Buffer.concat([r, i.serialize()]); - }); - - return r; -}; - -exports.deser_vector = function(f, c){ - var nit = f.read(1).readUInt8(0); - if (nit == 253) - nit = f.read(2).readUInt16LE(0); - else if (nit == 254) - nit = f.read(4).readUInt32LE(0); - else if (nit == 255) - nit = f.read(8).readUInt64LE(0); - var r = []; - exports.range(nit).forEach(function(i){ - var t = new c(); - t.deserialize(f); - r.push(t); - }); - return r; -}; - */ - +}; \ No newline at end of file