Added lots of comments, cleaned up a some code

This commit is contained in:
Matthew Little 2014-01-15 17:37:43 -07:00
parent 11338e0f16
commit e02b265d13
8 changed files with 95 additions and 170 deletions

View File

@ -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 ,''));
}
};
};

View File

@ -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

View File

@ -1,5 +1,4 @@
var net = require('net');
var fs = require('fs');
var events = require('events');
var pool = require('./pool.js');

View File

@ -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']);
}
}

View File

@ -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]);
}
};

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
};
*/
};