Added lots of comments, cleaned up a some code
This commit is contained in:
parent
11338e0f16
commit
e02b265d13
|
@ -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 ,''));
|
||||
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
var net = require('net');
|
||||
var fs = require('fs');
|
||||
var events = require('events');
|
||||
|
||||
var pool = require('./pool.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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
};
|
49
lib/pool.js
49
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
146
lib/util.js
146
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;
|
||||
};
|
||||
*/
|
||||
|
||||
};
|
Loading…
Reference in New Issue