Initial Z-NOMP Commit
This commit is contained in:
parent
02e78da1ab
commit
4cb9d21ccb
|
@ -2,7 +2,7 @@ var bignum = require('bignum');
|
||||||
var multiHashing = require('multi-hashing');
|
var multiHashing = require('multi-hashing');
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
var diff1 = global.diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000;
|
var diff1 = global.diff1 = 0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
|
||||||
|
|
||||||
var algos = module.exports = global.algos = {
|
var algos = module.exports = global.algos = {
|
||||||
sha256: {
|
sha256: {
|
||||||
|
@ -14,193 +14,18 @@ var algos = module.exports = global.algos = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'scrypt': {
|
'equihash': {
|
||||||
//Uncomment diff if you want to use hardcoded truncated diff
|
multiplier: 1,
|
||||||
//diff: '0000ffff00000000000000000000000000000000000000000000000000000000',
|
diff: parseInt('0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
|
||||||
multiplier: Math.pow(2, 16),
|
|
||||||
hash: function(coinConfig){
|
|
||||||
var nValue = coinConfig.nValue || 1024;
|
|
||||||
var rValue = coinConfig.rValue || 1;
|
|
||||||
return function(data){
|
|
||||||
return multiHashing.scrypt(data,nValue,rValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'scrypt-og': {
|
|
||||||
//Aiden settings
|
|
||||||
//Uncomment diff if you want to use hardcoded truncated diff
|
|
||||||
//diff: '0000ffff00000000000000000000000000000000000000000000000000000000',
|
|
||||||
multiplier: Math.pow(2, 16),
|
|
||||||
hash: function(coinConfig){
|
|
||||||
var nValue = coinConfig.nValue || 64;
|
|
||||||
var rValue = coinConfig.rValue || 1;
|
|
||||||
return function(data){
|
|
||||||
return multiHashing.scrypt(data,nValue,rValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'scrypt-jane': {
|
|
||||||
multiplier: Math.pow(2, 16),
|
|
||||||
hash: function(coinConfig){
|
|
||||||
var nTimestamp = coinConfig.chainStartTime || 1367991200;
|
|
||||||
var nMin = coinConfig.nMin || 4;
|
|
||||||
var nMax = coinConfig.nMax || 30;
|
|
||||||
return function(data, nTime){
|
|
||||||
return multiHashing.scryptjane(data, nTime, nTimestamp, nMin, nMax);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'scrypt-n': {
|
|
||||||
multiplier: Math.pow(2, 16),
|
|
||||||
hash: function(coinConfig){
|
|
||||||
|
|
||||||
var timeTable = coinConfig.timeTable || {
|
|
||||||
"2048": 1389306217, "4096": 1456415081, "8192": 1506746729, "16384": 1557078377, "32768": 1657741673,
|
|
||||||
"65536": 1859068265, "131072": 2060394857, "262144": 1722307603, "524288": 1769642992
|
|
||||||
};
|
|
||||||
|
|
||||||
var nFactor = (function(){
|
|
||||||
var n = Object.keys(timeTable).sort().reverse().filter(function(nKey){
|
|
||||||
return Date.now() / 1000 > timeTable[nKey];
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
var nInt = parseInt(n);
|
|
||||||
return Math.log(nInt) / Math.log(2);
|
|
||||||
})();
|
|
||||||
|
|
||||||
return function(data) {
|
|
||||||
return multiHashing.scryptn(data, nFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sha1: {
|
|
||||||
hash: function(){
|
hash: function(){
|
||||||
return function(){
|
return function(){
|
||||||
return multiHashing.sha1.apply(this, arguments);
|
return multiHashing.equihash.apply(this, arguments);
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x11: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.x11.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x13: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.x13.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
x15: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.x15.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nist5: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.nist5.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
quark: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.quark.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keccak: {
|
|
||||||
multiplier: Math.pow(2, 8),
|
|
||||||
hash: function(coinConfig){
|
|
||||||
if (coinConfig.normalHashing === true) {
|
|
||||||
return function (data, nTimeInt) {
|
|
||||||
return multiHashing.keccak(multiHashing.keccak(Buffer.concat([data, new Buffer(nTimeInt.toString(16), 'hex')])));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return function () {
|
|
||||||
return multiHashing.keccak.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
blake: {
|
|
||||||
multiplier: Math.pow(2, 8),
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.blake.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skein: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.skein.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
groestl: {
|
|
||||||
multiplier: Math.pow(2, 8),
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.groestl.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fugue: {
|
|
||||||
multiplier: Math.pow(2, 8),
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.fugue.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shavite3: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.shavite3.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hefty1: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.hefty1.apply(this, arguments);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
qubit: {
|
|
||||||
hash: function(){
|
|
||||||
return function(){
|
|
||||||
return multiHashing.qubit.apply(this, arguments);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
for (var algo in algos){
|
for (var algo in algos){
|
||||||
if (!algos[algo].multiplier)
|
if (!algos[algo].multiplier)
|
||||||
algos[algo].multiplier = 1;
|
algos[algo].multiplier = 1;
|
||||||
|
|
||||||
/*if (algos[algo].diff){
|
|
||||||
algos[algo].maxDiff = bignum(algos[algo].diff, 16);
|
|
||||||
}
|
|
||||||
else if (algos[algo].shift){
|
|
||||||
algos[algo].nonTruncatedDiff = util.shiftMax256Right(algos[algo].shift);
|
|
||||||
algos[algo].bits = util.bufferToCompactBits(algos[algo].nonTruncatedDiff);
|
|
||||||
algos[algo].maxDiff = bignum.fromBuffer(util.convertBitsToBuff(algos[algo].bits));
|
|
||||||
}
|
|
||||||
else if (algos[algo].multiplier){
|
|
||||||
algos[algo].maxDiff = diff1.mul(Math.pow(2, 32) / algos[algo].multiplier);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
algos[algo].maxDiff = diff1;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,136 +1,137 @@
|
||||||
var bignum = require('bignum');
|
var bignum = require('bignum');
|
||||||
|
|
||||||
var merkleTree = require('./merkleTree.js');
|
var merkle = require('./merkleTree.js');
|
||||||
var transactions = require('./transactions.js');
|
var transactions = require('./transactions.js');
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The BlockTemplate class holds a single job.
|
* The BlockTemplate class holds a single job.
|
||||||
* and provides several methods to validate and submit it to the daemon coin
|
* and provides several methods to validate and submit it to the daemon coin
|
||||||
**/
|
**/
|
||||||
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, poolAddressScript, extraNoncePlaceholder, reward, txMessages, recipients){
|
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, extraNoncePlaceholder, reward, recipients, poolAddress){
|
||||||
|
|
||||||
//private members
|
//private members
|
||||||
|
|
||||||
var submits = [];
|
var submits = [];
|
||||||
|
|
||||||
function getMerkleHashes(steps){
|
|
||||||
return steps.map(function(step){
|
|
||||||
return step.toString('hex');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTransactionBuffers(txs){
|
|
||||||
var txHashes = txs.map(function(tx){
|
|
||||||
return util.uint256BufferFromHash(tx.hash);
|
|
||||||
});
|
|
||||||
return [null].concat(txHashes);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVoteData(){
|
|
||||||
if (!rpcData.masternode_payments) return new Buffer([]);
|
|
||||||
|
|
||||||
return Buffer.concat(
|
|
||||||
[util.varIntBuffer(rpcData.votes.length)].concat(
|
|
||||||
rpcData.votes.map(function (vt) {
|
|
||||||
return new Buffer(vt, 'hex');
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//public members
|
//public members
|
||||||
|
|
||||||
this.rpcData = rpcData;
|
this.rpcData = rpcData;
|
||||||
this.jobId = jobId;
|
this.jobId = jobId;
|
||||||
|
|
||||||
|
// get target info
|
||||||
this.target = rpcData.target ?
|
this.target = bignum(rpcData.target, 16);
|
||||||
bignum(rpcData.target, 16) :
|
|
||||||
util.bignumFromBitsHex(rpcData.bits);
|
|
||||||
|
|
||||||
this.difficulty = parseFloat((diff1 / this.target.toNumber()).toFixed(9));
|
this.difficulty = parseFloat((diff1 / this.target.toNumber()).toFixed(9));
|
||||||
|
|
||||||
|
// generate the fees and coinbase tx
|
||||||
|
fees = [];
|
||||||
|
rpcData.transactions.forEach(function(value) {
|
||||||
|
fees.push(value);
|
||||||
|
});
|
||||||
|
this.reward = transactions.getFees(fees) + 1250000000; //FIXME: this should be calculated based off of getblocksubsidy
|
||||||
|
|
||||||
|
if (typeof this.genTx === 'undefined') {
|
||||||
|
this.genTx = transactions.createGeneration(rpcData.height, this.reward, recipients, poolAddress).toString('hex');
|
||||||
|
this.genTxHash = transactions.txHash();
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log('this.genTxHash: ' + transactions.txHash());
|
||||||
|
console.log('this.merkleRoot: ' + merkle.getRoot(rpcData, this.genTxHash));
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate the merkle root
|
||||||
|
this.prevHashReversed = util.reverseBuffer(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex');
|
||||||
|
this.merkleRoot = merkle.getRoot(rpcData, this.genTxHash);
|
||||||
|
this.txCount = this.rpcData.transactions.length + 1; // add total txs and new coinbase
|
||||||
|
this.merkleRootReversed = util.reverseBuffer(new Buffer(this.merkleRoot, 'hex')).toString('hex');
|
||||||
|
// we can't do anything else until we have a submission
|
||||||
|
|
||||||
this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex');
|
//block header per https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
|
||||||
this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){
|
this.serializeHeader = function(nTime, nonce){
|
||||||
return new Buffer(tx.data, 'hex');
|
var header = new Buffer(140);
|
||||||
}));
|
|
||||||
this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions));
|
|
||||||
this.merkleBranch = getMerkleHashes(this.merkleTree.steps);
|
|
||||||
this.generationTransaction = transactions.CreateGeneration(
|
|
||||||
rpcData,
|
|
||||||
poolAddressScript,
|
|
||||||
extraNoncePlaceholder,
|
|
||||||
reward,
|
|
||||||
txMessages,
|
|
||||||
recipients
|
|
||||||
);
|
|
||||||
|
|
||||||
this.serializeCoinbase = function(extraNonce1, extraNonce2){
|
|
||||||
return Buffer.concat([
|
|
||||||
this.generationTransaction[0],
|
|
||||||
extraNonce1,
|
|
||||||
extraNonce2,
|
|
||||||
this.generationTransaction[1]
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//https://en.bitcoin.it/wiki/Protocol_specification#Block_Headers
|
|
||||||
this.serializeHeader = function(merkleRoot, nTime, nonce){
|
|
||||||
|
|
||||||
var header = new Buffer(80);
|
|
||||||
var position = 0;
|
var position = 0;
|
||||||
header.write(nonce, position, 4, 'hex');
|
|
||||||
header.write(rpcData.bits, position += 4, 4, 'hex');
|
/*
|
||||||
header.write(nTime, position += 4, 4, 'hex');
|
console.log('nonce:' + nonce);
|
||||||
header.write(merkleRoot, position += 4, 32, 'hex');
|
console.log('this.rpcData.bits: ' + this.rpcData.bits);
|
||||||
header.write(rpcData.previousblockhash, position += 32, 32, 'hex');
|
console.log('nTime: ' + nTime);
|
||||||
header.writeUInt32BE(rpcData.version, position + 32);
|
console.log('this.merkleRootReversed: ' + this.merkleRoot);
|
||||||
var header = util.reverseBuffer(header);
|
console.log('this.prevHashReversed: ' + this.prevHashReversed);
|
||||||
|
console.log('this.rpcData.version: ' + this.rpcData.version);
|
||||||
|
*/
|
||||||
|
|
||||||
|
header.writeUInt32LE(this.rpcData.version, position += 0, 4, 'hex');
|
||||||
|
header.write(this.prevHashReversed, position += 4, 32, 'hex');
|
||||||
|
header.write(this.merkleRootReversed, position += 32, 32, 'hex');
|
||||||
|
header.write('0000000000000000000000000000000000000000000000000000000000000000', position += 32, 32, 'hex'); //hashReserved
|
||||||
|
header.write(nTime, position += 32, 4, 'hex');
|
||||||
|
header.write(util.reverseBuffer(Buffer(rpcData.bits, 'hex')).toString('hex'), position += 4, 4, 'hex');
|
||||||
|
header.write(nonce, position += 4, 32, 'hex');
|
||||||
return header;
|
return header;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.serializeBlock = function(header, coinbase){
|
// join the header and txs together
|
||||||
return Buffer.concat([
|
this.serializeBlock = function(header, soln){
|
||||||
|
if (this.txCount <= 0x7f){
|
||||||
|
if (this.txCount <= 0x7f) {
|
||||||
|
var txCount = '0' + this.txCount;
|
||||||
|
}
|
||||||
|
var varInt = new Buffer(txCount, 'hex');
|
||||||
|
}
|
||||||
|
else if (this.txCount <= 0x7fff){
|
||||||
|
if (this.txCount <= 0x7ff) {
|
||||||
|
var txCount = '0' + this.txCount;
|
||||||
|
}
|
||||||
|
var varInt = new Buffer.concat([Buffer('FD', 'hex'), Buffer(txCount, 'hex')]);
|
||||||
|
}
|
||||||
|
buf = Buffer.concat([
|
||||||
header,
|
header,
|
||||||
|
soln,
|
||||||
util.varIntBuffer(this.rpcData.transactions.length + 1),
|
varInt,
|
||||||
coinbase,
|
Buffer(this.genTx, 'hex')
|
||||||
this.transactionData,
|
|
||||||
|
|
||||||
getVoteData(),
|
|
||||||
|
|
||||||
//POS coins require a zero byte appended to block which the daemon replaces with the signature
|
|
||||||
new Buffer(reward === 'POS' ? [0] : [])
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (this.rpcData.transactions.length > 0) {
|
||||||
|
this.rpcData.transactions.forEach(function (value) {
|
||||||
|
tmpBuf = new Buffer.concat([buf, Buffer(value.data, 'hex')]);
|
||||||
|
buf = tmpBuf;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log('header: ' + header.toString('hex'));
|
||||||
|
console.log('soln: ' + soln.toString('hex'));
|
||||||
|
console.log('varInt: ' + varInt.toString('hex'));
|
||||||
|
console.log('this.genTx: ' + this.genTx);
|
||||||
|
console.log('data: ' + value.data);
|
||||||
|
console.log('buf_block: ' + buf.toString('hex'));
|
||||||
|
*/
|
||||||
|
|
||||||
|
return buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){
|
// submit the block header
|
||||||
var submission = extraNonce1 + extraNonce2 + nTime + nonce;
|
this.registerSubmit = function(header, soln){
|
||||||
|
var submission = header + soln;
|
||||||
if (submits.indexOf(submission) === -1){
|
if (submits.indexOf(submission) === -1){
|
||||||
|
|
||||||
submits.push(submission);
|
submits.push(submission);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// used for mining.notify
|
||||||
this.getJobParams = function(){
|
this.getJobParams = function(){
|
||||||
if (!this.jobParams){
|
if (!this.jobParams){
|
||||||
this.jobParams = [
|
this.jobParams = [
|
||||||
this.jobId,
|
this.jobId,
|
||||||
|
util.packUInt32LE(this.rpcData.version).toString('hex'),
|
||||||
this.prevHashReversed,
|
this.prevHashReversed,
|
||||||
this.generationTransaction[0].toString('hex'),
|
this.merkleRootReversed,
|
||||||
this.generationTransaction[1].toString('hex'),
|
'0000000000000000000000000000000000000000000000000000000000000000', //hashReserved
|
||||||
this.merkleBranch,
|
util.packUInt32LE(rpcData.curtime).toString('hex'),
|
||||||
util.packInt32BE(this.rpcData.version).toString('hex'),
|
util.reverseBuffer(Buffer(this.rpcData.bits, 'hex')).toString('hex'),
|
||||||
this.rpcData.bits,
|
|
||||||
util.packUInt32BE(this.rpcData.curtime).toString('hex'),
|
|
||||||
true
|
true
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,34 +11,34 @@ var async = require('async');
|
||||||
* - 'port' : port where the coin accepts rpc connections
|
* - 'port' : port where the coin accepts rpc connections
|
||||||
* - 'user' : username of the coin for the rpc interface
|
* - 'user' : username of the coin for the rpc interface
|
||||||
* - 'password': password for the rpc interface of the coin
|
* - 'password': password for the rpc interface of the coin
|
||||||
**/
|
**/
|
||||||
|
|
||||||
function DaemonInterface(daemons, logger){
|
function DaemonInterface(daemons, logger) {
|
||||||
|
|
||||||
//private members
|
//private members
|
||||||
var _this = this;
|
var _this = this;
|
||||||
logger = logger || function(severity, message){
|
logger = logger || function (severity, message) {
|
||||||
console.log(severity + ': ' + message);
|
console.log(severity + ': ' + message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var instances = (function(){
|
var instances = (function () {
|
||||||
for (var i = 0; i < daemons.length; i++)
|
for (var i = 0; i < daemons.length; i++)
|
||||||
daemons[i]['index'] = i;
|
daemons[i]['index'] = i;
|
||||||
return daemons;
|
return daemons;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
function init(){
|
function init() {
|
||||||
isOnline(function(online){
|
isOnline(function (online) {
|
||||||
if (online)
|
if (online)
|
||||||
_this.emit('online');
|
_this.emit('online');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function isOnline(callback){
|
function isOnline(callback) {
|
||||||
cmd('getinfo', [], function(results){
|
cmd('getinfo', [], function (results) {
|
||||||
var allOnline = results.every(function(result){
|
var allOnline = results.every(function (result) {
|
||||||
return !results.error;
|
return !results.error;
|
||||||
});
|
});
|
||||||
callback(allOnline);
|
callback(allOnline);
|
||||||
|
@ -48,30 +48,30 @@ function DaemonInterface(daemons, logger){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function performHttpRequest(instance, jsonData, callback){
|
function performHttpRequest(instance, jsonData, callback) {
|
||||||
var options = {
|
var options = {
|
||||||
hostname: (typeof(instance.host) === 'undefined' ? '127.0.0.1' : instance.host),
|
hostname: (typeof(instance.host) === 'undefined' ? '127.0.0.1' : instance.host),
|
||||||
port : instance.port,
|
port: instance.port,
|
||||||
method : 'POST',
|
method: 'POST',
|
||||||
auth : instance.user + ':' + instance.password,
|
auth: instance.user + ':' + instance.password,
|
||||||
headers : {
|
headers: {
|
||||||
'Content-Length': jsonData.length
|
'Content-Length': jsonData.length
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var parseJson = function(res, data){
|
var parseJson = function (res, data) {
|
||||||
var dataJson;
|
var dataJson;
|
||||||
|
|
||||||
if (res.statusCode === 401){
|
if (res.statusCode === 401) {
|
||||||
logger('error', 'Unauthorized RPC access - invalid RPC username or password');
|
logger('error', 'Unauthorized RPC access - invalid RPC username or password');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try {
|
||||||
dataJson = JSON.parse(data);
|
dataJson = JSON.parse(data);
|
||||||
}
|
}
|
||||||
catch(e){
|
catch (e) {
|
||||||
if (data.indexOf(':-nan') !== -1){
|
if (data.indexOf(':-nan') !== -1) {
|
||||||
data = data.replace(/:-nan,/g, ":0");
|
data = data.replace(/:-nan,/g, ":0");
|
||||||
parseJson(res, data);
|
parseJson(res, data);
|
||||||
return;
|
return;
|
||||||
|
@ -85,18 +85,18 @@ function DaemonInterface(daemons, logger){
|
||||||
callback(dataJson.error, dataJson, data);
|
callback(dataJson.error, dataJson, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
var req = http.request(options, function(res) {
|
var req = http.request(options, function (res) {
|
||||||
var data = '';
|
var data = '';
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
res.on('data', function (chunk) {
|
res.on('data', function (chunk) {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
});
|
});
|
||||||
res.on('end', function(){
|
res.on('end', function () {
|
||||||
parseJson(res, data);
|
parseJson(res, data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', function(e) {
|
req.on('error', function (e) {
|
||||||
if (e.code === 'ECONNREFUSED')
|
if (e.code === 'ECONNREFUSED')
|
||||||
callback({type: 'offline', message: e.message}, null);
|
callback({type: 'offline', message: e.message}, null);
|
||||||
else
|
else
|
||||||
|
@ -107,20 +107,19 @@ function DaemonInterface(daemons, logger){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Performs a batch JSON-RPC command - only uses the first configured rpc daemon
|
//Performs a batch JSON-RPC command - only uses the first configured rpc daemon
|
||||||
/* First argument must have:
|
/* First argument must have:
|
||||||
[
|
[
|
||||||
[ methodName, [params] ],
|
[ methodName, [params] ],
|
||||||
[ methodName, [params] ]
|
[ methodName, [params] ]
|
||||||
]
|
]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function batchCmd(cmdArray, callback){
|
function batchCmd(cmdArray, callback) {
|
||||||
|
|
||||||
var requestJson = [];
|
var requestJson = [];
|
||||||
|
|
||||||
for (var i = 0; i < cmdArray.length; i++){
|
for (var i = 0; i < cmdArray.length; i++) {
|
||||||
requestJson.push({
|
requestJson.push({
|
||||||
method: cmdArray[i][0],
|
method: cmdArray[i][0],
|
||||||
params: cmdArray[i][1],
|
params: cmdArray[i][1],
|
||||||
|
@ -130,22 +129,22 @@ function DaemonInterface(daemons, logger){
|
||||||
|
|
||||||
var serializedRequest = JSON.stringify(requestJson);
|
var serializedRequest = JSON.stringify(requestJson);
|
||||||
|
|
||||||
performHttpRequest(instances[0], serializedRequest, function(error, result){
|
performHttpRequest(instances[0], serializedRequest, function (error, result) {
|
||||||
callback(error, result);
|
callback(error, result);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sends a JSON RPC (http://json-rpc.org/wiki/specification) command to every configured daemon.
|
/* Sends a JSON RPC (http://json-rpc.org/wiki/specification) command to every configured daemon.
|
||||||
The callback function is fired once with the result from each daemon unless streamResults is
|
The callback function is fired once with the result from each daemon unless streamResults is
|
||||||
set to true. */
|
set to true. */
|
||||||
function cmd(method, params, callback, streamResults, returnRawData){
|
function cmd(method, params, callback, streamResults, returnRawData) {
|
||||||
|
|
||||||
var results = [];
|
var results = [];
|
||||||
|
|
||||||
async.each(instances, function(instance, eachCallback){
|
async.each(instances, function (instance, eachCallback) {
|
||||||
|
|
||||||
var itemFinished = function(error, result, data){
|
var itemFinished = function (error, result, data) {
|
||||||
|
|
||||||
var returnObj = {
|
var returnObj = {
|
||||||
error: error,
|
error: error,
|
||||||
|
@ -156,7 +155,8 @@ function DaemonInterface(daemons, logger){
|
||||||
if (streamResults) callback(returnObj);
|
if (streamResults) callback(returnObj);
|
||||||
else results.push(returnObj);
|
else results.push(returnObj);
|
||||||
eachCallback();
|
eachCallback();
|
||||||
itemFinished = function(){};
|
itemFinished = function () {
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var requestJson = JSON.stringify({
|
var requestJson = JSON.stringify({
|
||||||
|
@ -165,13 +165,13 @@ function DaemonInterface(daemons, logger){
|
||||||
id: Date.now() + Math.floor(Math.random() * 10)
|
id: Date.now() + Math.floor(Math.random() * 10)
|
||||||
});
|
});
|
||||||
|
|
||||||
performHttpRequest(instance, requestJson, function(error, result, data){
|
performHttpRequest(instance, requestJson, function (error, result, data) {
|
||||||
itemFinished(error, result, data);
|
itemFinished(error, result, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}, function(){
|
}, function () {
|
||||||
if (!streamResults){
|
if (!streamResults) {
|
||||||
callback(results);
|
callback(results);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ exports.daemon = require('./daemon.js');
|
||||||
exports.varDiff = require('./varDiff.js');
|
exports.varDiff = require('./varDiff.js');
|
||||||
|
|
||||||
|
|
||||||
exports.createPool = function(poolOptions, authorizeFn){
|
exports.createPool = function (poolOptions, authorizeFn) {
|
||||||
var newPool = new pool(poolOptions, authorizeFn);
|
var newPool = new pool(poolOptions, authorizeFn);
|
||||||
return newPool;
|
return newPool;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,33 +4,28 @@ var crypto = require('crypto');
|
||||||
var bignum = require('bignum');
|
var bignum = require('bignum');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
var blockTemplate = require('./blockTemplate.js');
|
var blockTemplate = require('./blockTemplate.js');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Unique extranonce per subscriber
|
//Unique extranonce per subscriber
|
||||||
var ExtraNonceCounter = function(configInstanceId){
|
var ExtraNonceCounter = function (configInstanceId) {
|
||||||
|
|
||||||
var instanceId = configInstanceId || crypto.randomBytes(4).readUInt32LE(0);
|
var instanceId = configInstanceId || crypto.randomBytes(4).readUInt32LE(0);
|
||||||
var counter = instanceId << 27;
|
var counter = instanceId << 27;
|
||||||
|
this.next = function () {
|
||||||
this.next = function(){
|
|
||||||
var extraNonce = util.packUInt32BE(Math.abs(counter++));
|
var extraNonce = util.packUInt32BE(Math.abs(counter++));
|
||||||
return extraNonce.toString('hex');
|
return extraNonce.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.size = 4; //bytes
|
this.size = 4; //bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
//Unique job per new block template
|
//Unique job per new block template
|
||||||
var JobCounter = function(){
|
var JobCounter = function () {
|
||||||
var counter = 0;
|
var counter = 0x0000cccc;
|
||||||
|
|
||||||
this.next = function(){
|
this.next = function () {
|
||||||
counter++;
|
counter++;
|
||||||
if (counter % 0xffff === 0)
|
if (counter % 0xffffffffff === 0)
|
||||||
counter = 1;
|
counter = 1;
|
||||||
return this.cur();
|
return this.cur();
|
||||||
};
|
};
|
||||||
|
@ -44,8 +39,8 @@ var JobCounter = function(){
|
||||||
* Emits:
|
* Emits:
|
||||||
* - newBlock(blockTemplate) - When a new block (previously unknown to the JobManager) is added, use this event to broadcast new jobs
|
* - newBlock(blockTemplate) - When a new block (previously unknown to the JobManager) is added, use this event to broadcast new jobs
|
||||||
* - share(shareData, blockHex) - When a worker submits a share. It will have blockHex if a block was found
|
* - share(shareData, blockHex) - When a worker submits a share. It will have blockHex if a block was found
|
||||||
**/
|
**/
|
||||||
var JobManager = module.exports = function JobManager(options){
|
var JobManager = module.exports = function JobManager(options) {
|
||||||
|
|
||||||
|
|
||||||
//private members
|
//private members
|
||||||
|
@ -58,24 +53,14 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
//public members
|
//public members
|
||||||
|
|
||||||
this.extraNonceCounter = new ExtraNonceCounter(options.instanceId);
|
this.extraNonceCounter = new ExtraNonceCounter(options.instanceId);
|
||||||
this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex');
|
|
||||||
this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size;
|
|
||||||
|
|
||||||
this.currentJob;
|
this.currentJob;
|
||||||
this.validJobs = {};
|
this.validJobs = {};
|
||||||
|
|
||||||
var hashDigest = algos[options.coin.algorithm].hash(options.coin);
|
var hashDigest = algos[options.coin.algorithm].hash(options.coin);
|
||||||
|
|
||||||
var coinbaseHasher = (function(){
|
var coinbaseHasher = (function () {
|
||||||
switch(options.coin.algorithm){
|
switch (options.coin.algorithm) {
|
||||||
case 'keccak':
|
|
||||||
case 'blake':
|
|
||||||
case 'fugue':
|
|
||||||
case 'groestl':
|
|
||||||
if (options.coin.normalHashing === true)
|
|
||||||
return util.sha256d;
|
|
||||||
else
|
|
||||||
return util.sha256;
|
|
||||||
default:
|
default:
|
||||||
return util.sha256d;
|
return util.sha256d;
|
||||||
}
|
}
|
||||||
|
@ -84,46 +69,25 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
|
|
||||||
var blockHasher = (function () {
|
var blockHasher = (function () {
|
||||||
switch (options.coin.algorithm) {
|
switch (options.coin.algorithm) {
|
||||||
case 'scrypt':
|
|
||||||
if (options.coin.reward === 'POS') {
|
|
||||||
return function (d) {
|
|
||||||
return util.reverseBuffer(hashDigest.apply(this, arguments));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'scrypt-og':
|
|
||||||
if (options.coin.reward === 'POS') {
|
|
||||||
return function (d) {
|
|
||||||
return util.reverseBuffer(hashDigest.apply(this, arguments));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'scrypt-jane':
|
|
||||||
if (options.coin.reward === 'POS') {
|
|
||||||
return function (d) {
|
|
||||||
return util.reverseBuffer(hashDigest.apply(this, arguments));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'scrypt-n':
|
|
||||||
case 'sha1':
|
case 'sha1':
|
||||||
return function (d) {
|
return function (d) {
|
||||||
return util.reverseBuffer(util.sha256d(d));
|
return util.reverseBuffer(util.sha256d(d));
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return function () {
|
return function (d) {
|
||||||
return util.reverseBuffer(hashDigest.apply(this, arguments));
|
return util.reverseBuffer(util.sha256(d));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
this.updateCurrentJob = function(rpcData){
|
this.updateCurrentJob = function (rpcData) {
|
||||||
|
|
||||||
var tmpBlockTemplate = new blockTemplate(
|
var tmpBlockTemplate = new blockTemplate(
|
||||||
jobCounter.next(),
|
jobCounter.next(),
|
||||||
rpcData,
|
rpcData,
|
||||||
options.poolAddressScript,
|
|
||||||
_this.extraNoncePlaceholder,
|
_this.extraNoncePlaceholder,
|
||||||
options.coin.reward,
|
options.coin.reward,
|
||||||
options.coin.txMessages,
|
options.recipients,
|
||||||
options.recipients
|
options.address
|
||||||
);
|
);
|
||||||
|
|
||||||
_this.currentJob = tmpBlockTemplate;
|
_this.currentJob = tmpBlockTemplate;
|
||||||
|
@ -135,12 +99,12 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
};
|
};
|
||||||
|
|
||||||
//returns true if processed a new block
|
//returns true if processed a new block
|
||||||
this.processTemplate = function(rpcData){
|
this.processTemplate = function (rpcData) {
|
||||||
|
|
||||||
/* Block is new if A) its the first block we have seen so far or B) the blockhash is different and the
|
/* Block is new if A) its the first block we have seen so far or B) the blockhash is different and the
|
||||||
block height is greater than the one we have */
|
block height is greater than the one we have */
|
||||||
var isNewBlock = typeof(_this.currentJob) === 'undefined';
|
var isNewBlock = typeof(_this.currentJob) === 'undefined';
|
||||||
if (!isNewBlock && _this.currentJob.rpcData.previousblockhash !== rpcData.previousblockhash){
|
if (!isNewBlock && _this.currentJob.rpcData.previousblockhash !== rpcData.previousblockhash) {
|
||||||
isNewBlock = true;
|
isNewBlock = true;
|
||||||
|
|
||||||
//If new block is outdated/out-of-sync than return
|
//If new block is outdated/out-of-sync than return
|
||||||
|
@ -154,11 +118,10 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
var tmpBlockTemplate = new blockTemplate(
|
var tmpBlockTemplate = new blockTemplate(
|
||||||
jobCounter.next(),
|
jobCounter.next(),
|
||||||
rpcData,
|
rpcData,
|
||||||
options.poolAddressScript,
|
|
||||||
_this.extraNoncePlaceholder,
|
_this.extraNoncePlaceholder,
|
||||||
options.coin.reward,
|
options.coin.reward,
|
||||||
options.coin.txMessages,
|
options.recipients,
|
||||||
options.recipients
|
options.address
|
||||||
);
|
);
|
||||||
|
|
||||||
this.currentJob = tmpBlockTemplate;
|
this.currentJob = tmpBlockTemplate;
|
||||||
|
@ -172,8 +135,8 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.processShare = function(jobId, previousDifficulty, difficulty, extraNonce1, extraNonce2, nTime, nonce, ipAddress, port, workerName){
|
this.processShare = function (jobId, previousDifficulty, difficulty, extraNonce1, extraNonce2, nTime, nonce, ipAddress, port, workerName, soln) {
|
||||||
var shareError = function(error){
|
var shareError = function (error) {
|
||||||
_this.emit('share', {
|
_this.emit('share', {
|
||||||
job: jobId,
|
job: jobId,
|
||||||
ip: ipAddress,
|
ip: ipAddress,
|
||||||
|
@ -186,12 +149,9 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
|
|
||||||
var submitTime = Date.now() / 1000 | 0;
|
var submitTime = Date.now() / 1000 | 0;
|
||||||
|
|
||||||
if (extraNonce2.length / 2 !== _this.extraNonce2Size)
|
|
||||||
return shareError([20, 'incorrect size of extranonce2']);
|
|
||||||
|
|
||||||
var job = this.validJobs[jobId];
|
var job = this.validJobs[jobId];
|
||||||
|
|
||||||
if (typeof job === 'undefined' || job.jobId != jobId ) {
|
if (typeof job === 'undefined' || job.jobId != jobId) {
|
||||||
return shareError([21, 'job not found']);
|
return shareError([21, 'job not found']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,30 +159,29 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
return shareError([20, 'incorrect size of ntime']);
|
return shareError([20, 'incorrect size of ntime']);
|
||||||
}
|
}
|
||||||
|
|
||||||
var nTimeInt = parseInt(nTime, 16);
|
var nTimeInt = parseInt(util.reverseBuffer(Buffer(nTime, 'hex')), 16);
|
||||||
if (nTimeInt < job.rpcData.curtime || nTimeInt > submitTime + 7200) {
|
if (nTimeInt < job.rpcData.curtime || nTimeInt > submitTime + 7200) {
|
||||||
return shareError([20, 'ntime out of range']);
|
return shareError([20, 'ntime out of range']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nonce.length !== 8) {
|
if (nonce.length !== 64) {
|
||||||
return shareError([20, 'incorrect size of nonce']);
|
return shareError([20, 'incorrect size of nonce']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (soln.length !== 2694) {
|
||||||
|
return shareError([20, 'incorrect size of solution']);
|
||||||
|
}
|
||||||
|
|
||||||
if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) {
|
if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) {
|
||||||
return shareError([22, 'duplicate share']);
|
return shareError([22, 'duplicate share']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var extraNonce1Buffer = new Buffer(extraNonce1, 'hex');
|
var extraNonce1Buffer = new Buffer(extraNonce1, 'hex');
|
||||||
var extraNonce2Buffer = new Buffer(extraNonce2, 'hex');
|
var extraNonce2Buffer = new Buffer(extraNonce2, 'hex');
|
||||||
|
|
||||||
var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer);
|
var headerBuffer = job.serializeHeader(nTime, nonce); // 144 bytes (doesn't contain soln)
|
||||||
var coinbaseHash = coinbaseHasher(coinbaseBuffer);
|
var headerSolnBuffer = Buffer.concat([headerBuffer, Buffer(soln, 'hex')]);
|
||||||
|
var headerHash = util.sha256d(headerSolnBuffer);
|
||||||
var merkleRoot = util.reverseBuffer(job.merkleTree.withFirst(coinbaseHash)).toString('hex');
|
|
||||||
|
|
||||||
var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce);
|
|
||||||
var headerHash = hashDigest(headerBuffer, nTimeInt);
|
|
||||||
var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32});
|
var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32});
|
||||||
|
|
||||||
var blockHashInvalid;
|
var blockHashInvalid;
|
||||||
|
@ -230,32 +189,47 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
var blockHex;
|
var blockHex;
|
||||||
|
|
||||||
var shareDiff = diff1 / headerBigNum.toNumber() * shareMultiplier;
|
var shareDiff = diff1 / headerBigNum.toNumber() * shareMultiplier;
|
||||||
|
|
||||||
var blockDiffAdjusted = job.difficulty * shareMultiplier;
|
var blockDiffAdjusted = job.difficulty * shareMultiplier;
|
||||||
|
|
||||||
//Check if share is a block candidate (matched network difficulty)
|
//check if block candidate and if the equihash solution is valid
|
||||||
if (job.target.ge(headerBigNum)){
|
//if (1 === 1) {
|
||||||
blockHex = job.serializeBlock(headerBuffer, coinbaseBuffer).toString('hex');
|
if (headerBigNum.le(job.target) && hashDigest(headerBuffer, Buffer(soln.slice(6), 'hex'))) {
|
||||||
blockHash = blockHasher(headerBuffer, nTime).toString('hex');
|
blockHex = job.serializeBlock(headerBuffer, Buffer(soln, 'hex')).toString('hex');
|
||||||
|
blockHash = blockHasher(headerBuffer, Buffer(soln, 'hex')).toString('hex');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (options.emitInvalidBlockHashes)
|
if (options.emitInvalidBlockHashes)
|
||||||
blockHashInvalid = util.reverseBuffer(util.sha256d(headerBuffer)).toString('hex');
|
blockHashInvalid = util.sha256d(headerBuffer).toString('hex');
|
||||||
|
|
||||||
//Check if share didn't reached the miner's difficulty)
|
//Check if share didn't reached the miner's difficulty)
|
||||||
if (shareDiff / difficulty < 0.99){
|
if (shareDiff / difficulty < 0.99) {
|
||||||
|
|
||||||
//Check if share matched a previous difficulty from before a vardiff retarget
|
//Check if share matched a previous difficulty from before a vardiff retarget
|
||||||
if (previousDifficulty && shareDiff >= previousDifficulty){
|
if (previousDifficulty && shareDiff >= previousDifficulty) {
|
||||||
difficulty = previousDifficulty;
|
difficulty = previousDifficulty;
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
return shareError([23, 'low difficulty share of ' + shareDiff]);
|
return shareError([23, 'low difficulty share of ' + shareDiff]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log('validSoln: ' + hashDigest(headerBuffer, Buffer(soln.slice(6), 'hex')));
|
||||||
|
console.log('job: ' + jobId);
|
||||||
|
console.log('ip: ' + ipAddress);
|
||||||
|
console.log('port: ' + port);
|
||||||
|
console.log('worker: ' + workerName);
|
||||||
|
console.log('height: ' + job.rpcData.height);
|
||||||
|
console.log('blockReward: ' + job.rpcData.coinbasevalue);
|
||||||
|
console.log('difficulty: ' + job.difficulty); // FIXME
|
||||||
|
console.log('shareDiff: ' + shareDiff.toFixed(8));
|
||||||
|
console.log('blockDiff: ' + blockDiffAdjusted);
|
||||||
|
console.log('blockDiffActual: ' + job.difficulty);
|
||||||
|
console.log('blockHash: ' + blockHash);
|
||||||
|
console.log('blockHashInvalid: ' + blockHashInvalid);
|
||||||
|
*/
|
||||||
|
|
||||||
_this.emit('share', {
|
_this.emit('share', {
|
||||||
job: jobId,
|
job: jobId,
|
||||||
|
@ -264,9 +238,9 @@ var JobManager = module.exports = function JobManager(options){
|
||||||
worker: workerName,
|
worker: workerName,
|
||||||
height: job.rpcData.height,
|
height: job.rpcData.height,
|
||||||
blockReward: job.rpcData.coinbasevalue,
|
blockReward: job.rpcData.coinbasevalue,
|
||||||
difficulty: difficulty,
|
difficulty: job.difficulty, // FIXME
|
||||||
shareDiff: shareDiff.toFixed(8),
|
shareDiff: shareDiff.toFixed(8),
|
||||||
blockDiff : blockDiffAdjusted,
|
blockDiff: blockDiffAdjusted,
|
||||||
blockDiffActual: job.difficulty,
|
blockDiffActual: job.difficulty,
|
||||||
blockHash: blockHash,
|
blockHash: blockHash,
|
||||||
blockHashInvalid: blockHashInvalid
|
blockHashInvalid: blockHashInvalid
|
||||||
|
|
|
@ -1,58 +1,22 @@
|
||||||
/*
|
var Promise = require('promise');
|
||||||
|
var merklebitcoin = Promise.denodeify(require('merkle-bitcoin'));
|
||||||
Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
var MerkleTree = module.exports = function MerkleTree(data){
|
|
||||||
|
|
||||||
function merkleJoin(h1, h2){
|
|
||||||
var joined = Buffer.concat([h1, h2]);
|
|
||||||
var dhashed = util.sha256d(joined);
|
|
||||||
return dhashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateSteps(data){
|
|
||||||
var L = data;
|
|
||||||
var steps = [];
|
|
||||||
var PreL = [null];
|
|
||||||
var StartL = 2;
|
|
||||||
var Ll = L.length;
|
|
||||||
|
|
||||||
if (Ll > 1){
|
|
||||||
while (true){
|
|
||||||
|
|
||||||
if (Ll === 1)
|
|
||||||
break;
|
|
||||||
|
|
||||||
steps.push(L[1]);
|
|
||||||
|
|
||||||
if (Ll % 2)
|
|
||||||
L.push(L[L.length - 1]);
|
|
||||||
|
|
||||||
var Ld = [];
|
|
||||||
var r = util.range(StartL, Ll, 2);
|
|
||||||
r.forEach(function(i){
|
|
||||||
Ld.push(merkleJoin(L[i], L[i + 1]));
|
|
||||||
});
|
|
||||||
L = PreL.concat(Ld);
|
|
||||||
Ll = L.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = data;
|
|
||||||
this.steps = calculateSteps(data);
|
|
||||||
|
|
||||||
|
function calcRoot(hashes) {
|
||||||
|
var result = merklebitcoin(hashes);
|
||||||
|
return result._65.root;
|
||||||
}
|
}
|
||||||
MerkleTree.prototype = {
|
|
||||||
withFirst: function(f){
|
exports.getRoot = function (rpcData, generateTxRaw) {
|
||||||
this.steps.forEach(function(s){
|
hashes = [generateTxRaw];
|
||||||
f = util.sha256d(Buffer.concat([f, s]));
|
rpcData.transactions.forEach(function (value) {
|
||||||
});
|
hashes.push(value.hash);
|
||||||
return f;
|
});
|
||||||
|
if (hashes.length === 1) {
|
||||||
|
result = util.reverseBuffer(Buffer(hashes[0], 'hex')).toString('hex');
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
var result = calcRoot(hashes);
|
||||||
|
return result;
|
||||||
};
|
};
|
17
lib/peer.js
17
lib/peer.js
|
@ -8,7 +8,7 @@ var util = require('./util.js');
|
||||||
//Example of p2p in node from TheSeven: http://paste.pm/e54.js
|
//Example of p2p in node from TheSeven: http://paste.pm/e54.js
|
||||||
|
|
||||||
|
|
||||||
var fixedLenStringBuffer = function(s, len) {
|
var fixedLenStringBuffer = function (s, len) {
|
||||||
var buff = new Buffer(len);
|
var buff = new Buffer(len);
|
||||||
buff.fill(0);
|
buff.fill(0);
|
||||||
buff.write(s);
|
buff.write(s);
|
||||||
|
@ -20,10 +20,10 @@ var commandStringBuffer = function (s) {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Reads a set amount of bytes from a flowing stream, argument descriptions:
|
/* Reads a set amount of bytes from a flowing stream, argument descriptions:
|
||||||
- stream to read from, must have data emitter
|
- stream to read from, must have data emitter
|
||||||
- amount of bytes to read
|
- amount of bytes to read
|
||||||
- preRead argument can be used to set start with an existing data buffer
|
- preRead argument can be used to set start with an existing data buffer
|
||||||
- callback returns 1) data buffer and 2) lopped/over-read data */
|
- callback returns 1) data buffer and 2) lopped/over-read data */
|
||||||
var readFlowingBytes = function (stream, amount, preRead, callback) {
|
var readFlowingBytes = function (stream, amount, preRead, callback) {
|
||||||
|
|
||||||
var buff = preRead ? preRead : new Buffer([]);
|
var buff = preRead ? preRead : new Buffer([]);
|
||||||
|
@ -155,13 +155,12 @@ var Peer = module.exports = function (options) {
|
||||||
//sloppy varint decoding
|
//sloppy varint decoding
|
||||||
var count = payload.readUInt8(0);
|
var count = payload.readUInt8(0);
|
||||||
payload = payload.slice(1);
|
payload = payload.slice(1);
|
||||||
if (count >= 0xfd)
|
if (count >= 0xfd) {
|
||||||
{
|
|
||||||
count = payload.readUInt16LE(0);
|
count = payload.readUInt16LE(0);
|
||||||
payload = payload.slice(2);
|
payload = payload.slice(2);
|
||||||
}
|
}
|
||||||
while (count--) {
|
while (count--) {
|
||||||
switch(payload.readUInt32LE(0)) {
|
switch (payload.readUInt32LE(0)) {
|
||||||
case invCodes.error:
|
case invCodes.error:
|
||||||
break;
|
break;
|
||||||
case invCodes.tx:
|
case invCodes.tx:
|
||||||
|
@ -183,7 +182,7 @@ var Peer = module.exports = function (options) {
|
||||||
HandleInv(payload);
|
HandleInv(payload);
|
||||||
break;
|
break;
|
||||||
case commands.verack.toString():
|
case commands.verack.toString():
|
||||||
if(!verack) {
|
if (!verack) {
|
||||||
verack = true;
|
verack = true;
|
||||||
_this.emit('connected');
|
_this.emit('connected');
|
||||||
}
|
}
|
||||||
|
|
377
lib/pool.js
377
lib/pool.js
|
@ -9,11 +9,11 @@ var jobManager = require('./jobManager.js');
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
/*process.on('uncaughtException', function(err) {
|
/*process.on('uncaughtException', function(err) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
throw err;
|
throw err;
|
||||||
});*/
|
});*/
|
||||||
|
|
||||||
var pool = module.exports = function pool(options, authorizeFn){
|
var pool = module.exports = function pool(options, authorizeFn) {
|
||||||
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
|
||||||
|
@ -21,32 +21,38 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
var blockPollingIntervalId;
|
var blockPollingIntervalId;
|
||||||
|
|
||||||
|
|
||||||
var emitLog = function(text) { _this.emit('log', 'debug' , text); };
|
var emitLog = function (text) {
|
||||||
var emitWarningLog = function(text) { _this.emit('log', 'warning', text); };
|
_this.emit('log', 'debug', text);
|
||||||
var emitErrorLog = function(text) { _this.emit('log', 'error' , text); };
|
};
|
||||||
var emitSpecialLog = function(text) { _this.emit('log', 'special', text); };
|
var emitWarningLog = function (text) {
|
||||||
|
_this.emit('log', 'warning', text);
|
||||||
|
};
|
||||||
|
var emitErrorLog = function (text) {
|
||||||
|
_this.emit('log', 'error', text);
|
||||||
|
};
|
||||||
|
var emitSpecialLog = function (text) {
|
||||||
|
_this.emit('log', 'special', text);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (!(options.coin.algorithm in algos)) {
|
||||||
if (!(options.coin.algorithm in algos)){
|
|
||||||
emitErrorLog('The ' + options.coin.algorithm + ' hashing algorithm is not supported.');
|
emitErrorLog('The ' + options.coin.algorithm + ' hashing algorithm is not supported.');
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.start = function () {
|
||||||
this.start = function(){
|
|
||||||
SetupVarDiff();
|
SetupVarDiff();
|
||||||
SetupApi();
|
SetupApi();
|
||||||
SetupDaemonInterface(function(){
|
SetupDaemonInterface(function () {
|
||||||
DetectCoinData(function(){
|
DetectCoinData(function () {
|
||||||
SetupRecipients();
|
SetupRecipients();
|
||||||
SetupJobManager();
|
SetupJobManager();
|
||||||
OnBlockchainSynced(function(){
|
OnBlockchainSynced(function () {
|
||||||
GetFirstJob(function(){
|
GetFirstJob(function () {
|
||||||
SetupBlockPolling();
|
SetupBlockPolling();
|
||||||
SetupPeer();
|
SetupPeer();
|
||||||
StartStratumServer(function(){
|
StartStratumServer(function () {
|
||||||
OutputPoolInfo();
|
OutputPoolInfo();
|
||||||
_this.emit('started');
|
_this.emit('started');
|
||||||
});
|
});
|
||||||
|
@ -57,10 +63,9 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function GetFirstJob(finishedCallback) {
|
||||||
|
|
||||||
function GetFirstJob(finishedCallback){
|
GetBlockTemplate(function (error, result) {
|
||||||
|
|
||||||
GetBlockTemplate(function(error, result){
|
|
||||||
if (error) {
|
if (error) {
|
||||||
emitErrorLog('Error with getblocktemplate on creating first job, server cannot start');
|
emitErrorLog('Error with getblocktemplate on creating first job, server cannot start');
|
||||||
return;
|
return;
|
||||||
|
@ -70,7 +75,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
var networkDiffAdjusted = options.initStats.difficulty;
|
var networkDiffAdjusted = options.initStats.difficulty;
|
||||||
|
|
||||||
Object.keys(options.ports).forEach(function(port){
|
Object.keys(options.ports).forEach(function (port) {
|
||||||
var portDiff = options.ports[port].diff;
|
var portDiff = options.ports[port].diff;
|
||||||
if (networkDiffAdjusted < portDiff)
|
if (networkDiffAdjusted < portDiff)
|
||||||
portWarnings.push('port ' + port + ' w/ diff ' + portDiff);
|
portWarnings.push('port ' + port + ' w/ diff ' + portDiff);
|
||||||
|
@ -89,24 +94,24 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function OutputPoolInfo(){
|
function OutputPoolInfo() {
|
||||||
|
|
||||||
var startMessage = 'Stratum Pool Server Started for ' + options.coin.name +
|
var startMessage = 'Stratum Pool Server Started for ' + options.coin.name +
|
||||||
' [' + options.coin.symbol.toUpperCase() + '] {' + options.coin.algorithm + '}';
|
' [' + options.coin.symbol.toUpperCase() + '] {' + options.coin.algorithm + '}';
|
||||||
if (process.env.forkId && process.env.forkId !== '0'){
|
if (process.env.forkId && process.env.forkId !== '0') {
|
||||||
emitLog(startMessage);
|
emitLog(startMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var infoLines = [startMessage,
|
var infoLines = [startMessage,
|
||||||
'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'),
|
'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'),
|
||||||
'Detected Reward Type:\t' + options.coin.reward,
|
'Detected Reward Type:\t' + options.coin.reward,
|
||||||
'Current Block Height:\t' + _this.jobManager.currentJob.rpcData.height,
|
'Current Block Height:\t' + _this.jobManager.currentJob.rpcData.height,
|
||||||
'Current Connect Peers:\t' + options.initStats.connections,
|
'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier,
|
||||||
'Current Block Diff:\t' + _this.jobManager.currentJob.difficulty * algos[options.coin.algorithm].multiplier,
|
'Current Connect Peers:\t' + options.initStats.connections,
|
||||||
'Network Difficulty:\t' + options.initStats.difficulty,
|
'Network Difficulty:\t' + options.initStats.difficulty,
|
||||||
'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate),
|
'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate),
|
||||||
'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '),
|
'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '),
|
||||||
'Pool Fee Percent:\t' + _this.options.feePercent + '%'
|
'Pool Fee Percent:\t' + _this.options.feePercent + '%'
|
||||||
];
|
];
|
||||||
|
|
||||||
if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0)
|
if (typeof options.blockRefreshInterval === "number" && options.blockRefreshInterval > 0)
|
||||||
|
@ -116,17 +121,17 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function OnBlockchainSynced(syncedCallback){
|
function OnBlockchainSynced(syncedCallback) {
|
||||||
|
|
||||||
var checkSynced = function(displayNotSynced){
|
var checkSynced = function (displayNotSynced) {
|
||||||
_this.daemon.cmd('getblocktemplate', [], function(results){
|
_this.daemon.cmd('getblocktemplate', [], function (results) {
|
||||||
var synced = results.every(function(r){
|
var synced = results.every(function (r) {
|
||||||
return !r.error || r.error.code !== -10;
|
return !r.error || r.error.code !== -10;
|
||||||
});
|
});
|
||||||
if (synced){
|
if (synced) {
|
||||||
syncedCallback();
|
syncedCallback();
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
if (displayNotSynced) displayNotSynced();
|
if (displayNotSynced) displayNotSynced();
|
||||||
setTimeout(checkSynced, 5000);
|
setTimeout(checkSynced, 5000);
|
||||||
|
|
||||||
|
@ -137,25 +142,25 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
checkSynced(function(){
|
checkSynced(function () {
|
||||||
//Only let the first fork show synced status or the log wil look flooded with it
|
//Only let the first fork show synced status or the log wil look flooded with it
|
||||||
if (!process.env.forkId || process.env.forkId === '0')
|
if (!process.env.forkId || process.env.forkId === '0')
|
||||||
emitErrorLog('Daemon is still syncing with network (download blockchain) - server will be started once synced');
|
emitErrorLog('Daemon is still syncing with network (download blockchain) - server will be started once synced');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var generateProgress = function(){
|
var generateProgress = function () {
|
||||||
|
|
||||||
_this.daemon.cmd('getinfo', [], function(results) {
|
_this.daemon.cmd('getinfo', [], function (results) {
|
||||||
var blockCount = results.sort(function (a, b) {
|
var blockCount = results.sort(function (a, b) {
|
||||||
return b.response.blocks - a.response.blocks;
|
return b.response.blocks - a.response.blocks;
|
||||||
})[0].response.blocks;
|
})[0].response.blocks;
|
||||||
|
|
||||||
//get list of peers and their highest block height to compare to ours
|
//get list of peers and their highest block height to compare to ours
|
||||||
_this.daemon.cmd('getpeerinfo', [], function(results){
|
_this.daemon.cmd('getpeerinfo', [], function (results) {
|
||||||
|
|
||||||
var peers = results[0].response;
|
var peers = results[0].response;
|
||||||
var totalBlocks = peers.sort(function(a, b){
|
var totalBlocks = peers.sort(function (a, b) {
|
||||||
return b.startingheight - a.startingheight;
|
return b.startingheight - a.startingheight;
|
||||||
})[0].startingheight;
|
})[0].startingheight;
|
||||||
|
|
||||||
|
@ -171,48 +176,47 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
function SetupApi() {
|
function SetupApi() {
|
||||||
if (typeof(options.api) !== 'object' || typeof(options.api.start) !== 'function') {
|
if (typeof(options.api) !== 'object' || typeof(options.api.start) !== 'function') {
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
options.api.start(_this);
|
options.api.start(_this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function SetupPeer(){
|
function SetupPeer() {
|
||||||
if (!options.p2p || !options.p2p.enabled)
|
if (!options.p2p || !options.p2p.enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (options.testnet && !options.coin.peerMagicTestnet){
|
if (options.testnet && !options.coin.peerMagicTestnet) {
|
||||||
emitErrorLog('p2p cannot be enabled in testnet without peerMagicTestnet set in coin configuration');
|
emitErrorLog('p2p cannot be enabled in testnet without peerMagicTestnet set in coin configuration');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!options.coin.peerMagic){
|
else if (!options.coin.peerMagic) {
|
||||||
emitErrorLog('p2p cannot be enabled without peerMagic set in coin configuration');
|
emitErrorLog('p2p cannot be enabled without peerMagic set in coin configuration');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_this.peer = new peer(options);
|
_this.peer = new peer(options);
|
||||||
_this.peer.on('connected', function() {
|
_this.peer.on('connected', function () {
|
||||||
emitLog('p2p connection successful');
|
emitLog('p2p connection successful');
|
||||||
}).on('connectionRejected', function(){
|
}).on('connectionRejected', function () {
|
||||||
emitErrorLog('p2p connection failed - likely incorrect p2p magic value');
|
emitErrorLog('p2p connection failed - likely incorrect p2p magic value');
|
||||||
}).on('disconnected', function(){
|
}).on('disconnected', function () {
|
||||||
emitWarningLog('p2p peer node disconnected - attempting reconnection...');
|
emitWarningLog('p2p peer node disconnected - attempting reconnection...');
|
||||||
}).on('connectionFailed', function(e){
|
}).on('connectionFailed', function (e) {
|
||||||
emitErrorLog('p2p connection failed - likely incorrect host or port');
|
emitErrorLog('p2p connection failed - likely incorrect host or port');
|
||||||
}).on('socketError', function(e){
|
}).on('socketError', function (e) {
|
||||||
emitErrorLog('p2p had a socket error ' + JSON.stringify(e));
|
emitErrorLog('p2p had a socket error ' + JSON.stringify(e));
|
||||||
}).on('error', function(msg){
|
}).on('error', function (msg) {
|
||||||
emitWarningLog('p2p had an error ' + msg);
|
emitWarningLog('p2p had an error ' + msg);
|
||||||
}).on('blockFound', function(hash){
|
}).on('blockFound', function (hash) {
|
||||||
_this.processBlockNotify(hash, 'p2p');
|
_this.processBlockNotify(hash, 'p2p');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function SetupVarDiff(){
|
function SetupVarDiff() {
|
||||||
_this.varDiff = {};
|
_this.varDiff = {};
|
||||||
Object.keys(options.ports).forEach(function(port) {
|
Object.keys(options.ports).forEach(function (port) {
|
||||||
if (options.ports[port].varDiff)
|
if (options.ports[port].varDiff)
|
||||||
_this.setVarDiff(port, options.ports[port].varDiff);
|
_this.setVarDiff(port, options.ports[port].varDiff);
|
||||||
});
|
});
|
||||||
|
@ -220,16 +224,16 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Coin daemons either use submitblock or getblocktemplate for submitting new blocks
|
Coin daemons either use submitblock or getblocktemplate for submitting new blocks
|
||||||
*/
|
*/
|
||||||
function SubmitBlock(blockHex, callback){
|
function SubmitBlock(blockHex, callback) {
|
||||||
|
|
||||||
var rpcCommand, rpcArgs;
|
var rpcCommand, rpcArgs;
|
||||||
if (options.hasSubmitMethod){
|
if (options.hasSubmitMethod) {
|
||||||
rpcCommand = 'submitblock';
|
rpcCommand = 'submitblock';
|
||||||
rpcArgs = [blockHex];
|
rpcArgs = [blockHex];
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
rpcCommand = 'getblocktemplate';
|
rpcCommand = 'getblocktemplate';
|
||||||
rpcArgs = [{'mode': 'submit', 'data': blockHex}];
|
rpcArgs = [{'mode': 'submit', 'data': blockHex}];
|
||||||
}
|
}
|
||||||
|
@ -237,13 +241,13 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
_this.daemon.cmd(rpcCommand,
|
_this.daemon.cmd(rpcCommand,
|
||||||
rpcArgs,
|
rpcArgs,
|
||||||
function(results){
|
function (results) {
|
||||||
for (var i = 0; i < results.length; i++){
|
for (var i = 0; i < results.length; i++) {
|
||||||
var result = results[i];
|
var result = results[i];
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
emitErrorLog('rpc error with daemon instance ' +
|
emitErrorLog('rpc error with daemon instance ' +
|
||||||
result.instance.index + ' when submitting block with ' + rpcCommand + ' ' +
|
result.instance.index + ' when submitting block with ' + rpcCommand + ' ' +
|
||||||
JSON.stringify(result.error)
|
JSON.stringify(result.error)
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -256,15 +260,16 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SetupRecipients() {
|
||||||
function SetupRecipients(){
|
|
||||||
var recipients = [];
|
var recipients = [];
|
||||||
options.feePercent = 0;
|
options.feePercent = 0;
|
||||||
options.rewardRecipients = options.rewardRecipients || {};
|
//options.rewardRecipients = options.rewardRecipients || {};
|
||||||
for (var r in options.rewardRecipients){
|
options.rewardRecipients = options.recipients;
|
||||||
|
|
||||||
|
/*
|
||||||
|
for (var r in options.rewardRecipients) {
|
||||||
var percent = options.rewardRecipients[r];
|
var percent = options.rewardRecipients[r];
|
||||||
var rObj = {
|
var rObj = {
|
||||||
percent: percent / 100
|
percent: percent / 100
|
||||||
|
@ -277,54 +282,55 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
recipients.push(rObj);
|
recipients.push(rObj);
|
||||||
options.feePercent += percent;
|
options.feePercent += percent;
|
||||||
}
|
}
|
||||||
catch(e){
|
catch (e) {
|
||||||
emitErrorLog('Error generating transaction output script for ' + r + ' in rewardRecipients');
|
emitErrorLog('Error generating transaction output script for ' + r + ' in rewardRecipients');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (recipients.length === 0){
|
*/
|
||||||
|
if (recipients.length === 0) {
|
||||||
emitErrorLog('No rewardRecipients have been setup which means no fees will be taken');
|
emitErrorLog('No rewardRecipients have been setup which means no fees will be taken');
|
||||||
}
|
}
|
||||||
options.recipients = recipients;
|
options.recipients = recipients;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetupJobManager(){
|
function SetupJobManager() {
|
||||||
|
|
||||||
_this.jobManager = new jobManager(options);
|
_this.jobManager = new jobManager(options);
|
||||||
|
|
||||||
_this.jobManager.on('newBlock', function(blockTemplate){
|
_this.jobManager.on('newBlock', function (blockTemplate) {
|
||||||
//Check if stratumServer has been initialized yet
|
//Check if stratumServer has been initialized yet
|
||||||
if (_this.stratumServer) {
|
if (_this.stratumServer) {
|
||||||
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
|
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
|
||||||
}
|
}
|
||||||
}).on('updatedBlock', function(blockTemplate){
|
}).on('updatedBlock', function (blockTemplate) {
|
||||||
//Check if stratumServer has been initialized yet
|
//Check if stratumServer has been initialized yet
|
||||||
if (_this.stratumServer) {
|
if (_this.stratumServer) {
|
||||||
var job = blockTemplate.getJobParams();
|
var job = blockTemplate.getJobParams();
|
||||||
job[8] = false;
|
job[8] = false;
|
||||||
_this.stratumServer.broadcastMiningJobs(job);
|
_this.stratumServer.broadcastMiningJobs(job);
|
||||||
}
|
}
|
||||||
}).on('share', function(shareData, blockHex){
|
}).on('share', function (shareData, blockHex) {
|
||||||
var isValidShare = !shareData.error;
|
var isValidShare = !shareData.error;
|
||||||
var isValidBlock = !!blockHex;
|
var isValidBlock = !!blockHex;
|
||||||
var emitShare = function(){
|
var emitShare = function () {
|
||||||
_this.emit('share', isValidShare, isValidBlock, shareData);
|
_this.emit('share', isValidShare, isValidBlock, shareData);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If we calculated that the block solution was found,
|
If we calculated that the block solution was found,
|
||||||
before we emit the share, lets submit the block,
|
before we emit the share, lets submit the block,
|
||||||
then check if it was accepted using RPC getblock
|
then check if it was accepted using RPC getblock
|
||||||
*/
|
*/
|
||||||
if (!isValidBlock)
|
if (!isValidBlock)
|
||||||
emitShare();
|
emitShare();
|
||||||
else{
|
else {
|
||||||
SubmitBlock(blockHex, function(){
|
SubmitBlock(blockHex, function () {
|
||||||
CheckBlockAccepted(shareData.blockHash, function(isAccepted, tx){
|
CheckBlockAccepted(shareData.blockHash, function (isAccepted, tx) {
|
||||||
isValidBlock = isAccepted;
|
isValidBlock = isAccepted;
|
||||||
shareData.txHash = tx;
|
shareData.txHash = tx;
|
||||||
emitShare();
|
emitShare();
|
||||||
|
|
||||||
GetBlockTemplate(function(error, result, foundNewBlock){
|
GetBlockTemplate(function (error, result, foundNewBlock) {
|
||||||
if (foundNewBlock)
|
if (foundNewBlock)
|
||||||
emitLog('Block notification via RPC after block submission');
|
emitLog('Block notification via RPC after block submission');
|
||||||
});
|
});
|
||||||
|
@ -332,30 +338,30 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).on('log', function(severity, message){
|
}).on('log', function (severity, message) {
|
||||||
_this.emit('log', severity, message);
|
_this.emit('log', severity, message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function SetupDaemonInterface(finishedCallback){
|
function SetupDaemonInterface(finishedCallback) {
|
||||||
|
|
||||||
if (!Array.isArray(options.daemons) || options.daemons.length < 1){
|
if (!Array.isArray(options.daemons) || options.daemons.length < 1) {
|
||||||
emitErrorLog('No daemons have been configured - pool cannot start');
|
emitErrorLog('No daemons have been configured - pool cannot start');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_this.daemon = new daemon.interface(options.daemons, function(severity, message){
|
_this.daemon = new daemon.interface(options.daemons, function (severity, message) {
|
||||||
_this.emit('log', severity , message);
|
_this.emit('log', severity, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
_this.daemon.once('online', function(){
|
_this.daemon.once('online', function () {
|
||||||
finishedCallback();
|
finishedCallback();
|
||||||
|
|
||||||
}).on('connectionFailed', function(error){
|
}).on('connectionFailed', function (error) {
|
||||||
emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error));
|
emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error));
|
||||||
|
|
||||||
}).on('error', function(message){
|
}).on('error', function (message) {
|
||||||
emitErrorLog(message);
|
emitErrorLog(message);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -364,7 +370,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function DetectCoinData(finishedCallback){
|
function DetectCoinData(finishedCallback) {
|
||||||
|
|
||||||
var batchRpcCalls = [
|
var batchRpcCalls = [
|
||||||
['validateaddress', [options.address]],
|
['validateaddress', [options.address]],
|
||||||
|
@ -374,26 +380,26 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
['submitblock', []]
|
['submitblock', []]
|
||||||
];
|
];
|
||||||
|
|
||||||
_this.daemon.batchCmd(batchRpcCalls, function(error, results){
|
_this.daemon.batchCmd(batchRpcCalls, function (error, results) {
|
||||||
if (error || !results){
|
if (error || !results) {
|
||||||
emitErrorLog('Could not start pool, error with init batch RPC call: ' + JSON.stringify(error));
|
emitErrorLog('Could not start pool, error with init batch RPC call: ' + JSON.stringify(error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcResults = {};
|
var rpcResults = {};
|
||||||
|
|
||||||
for (var i = 0; i < results.length; i++){
|
for (var i = 0; i < results.length; i++) {
|
||||||
var rpcCall = batchRpcCalls[i][0];
|
var rpcCall = batchRpcCalls[i][0];
|
||||||
var r = results[i];
|
var r = results[i];
|
||||||
rpcResults[rpcCall] = r.result || r.error;
|
rpcResults[rpcCall] = r.result || r.error;
|
||||||
|
|
||||||
if (rpcCall !== 'submitblock' && (r.error || !r.result)){
|
if (rpcCall !== 'submitblock' && (r.error || !r.result)) {
|
||||||
emitErrorLog('Could not start pool, error with init RPC ' + rpcCall + ' - ' + JSON.stringify(r.error));
|
emitErrorLog('Could not start pool, error with init RPC ' + rpcCall + ' - ' + JSON.stringify(r.error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rpcResults.validateaddress.isvalid){
|
if (!rpcResults.validateaddress.isvalid) {
|
||||||
emitErrorLog('Daemon reports address is not valid');
|
emitErrorLog('Daemon reports address is not valid');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -407,19 +413,14 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
|
|
||||||
|
|
||||||
/* POS coins must use the pubkey in coinbase transaction, and pubkey is
|
/* POS coins must use the pubkey in coinbase transaction, and pubkey is
|
||||||
only given if address is owned by wallet.*/
|
only given if address is owned by wallet.*/
|
||||||
if (options.coin.reward === 'POS' && typeof(rpcResults.validateaddress.pubkey) == 'undefined') {
|
if (options.coin.reward === 'POS' && typeof(rpcResults.validateaddress.pubkey) == 'undefined') {
|
||||||
emitErrorLog('The address provided is not from the daemon wallet - this is required for POS coins.');
|
emitErrorLog('The address provided is not from the daemon wallet - this is required for POS coins.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.poolAddressScript = (function(){
|
options.poolAddressScript = (function () {
|
||||||
switch(options.coin.reward){
|
return util.addressToScript(rpcResults.validateaddress.address);
|
||||||
case 'POS':
|
|
||||||
return util.pubkeyToScript(rpcResults.validateaddress.pubkey);
|
|
||||||
case 'POW':
|
|
||||||
return util.addressToScript(rpcResults.validateaddress.address);
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
options.testnet = rpcResults.getinfo.testnet;
|
options.testnet = rpcResults.getinfo.testnet;
|
||||||
|
@ -432,10 +433,10 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (rpcResults.submitblock.message === 'Method not found'){
|
if (rpcResults.submitblock.message === 'Method not found') {
|
||||||
options.hasSubmitMethod = false;
|
options.hasSubmitMethod = false;
|
||||||
}
|
}
|
||||||
else if (rpcResults.submitblock.code === -1){
|
else if (rpcResults.submitblock.code === -1) {
|
||||||
options.hasSubmitMethod = true;
|
options.hasSubmitMethod = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -449,50 +450,50 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function StartStratumServer(finishedCallback) {
|
||||||
function StartStratumServer(finishedCallback){
|
|
||||||
_this.stratumServer = new stratum.Server(options, authorizeFn);
|
_this.stratumServer = new stratum.Server(options, authorizeFn);
|
||||||
|
|
||||||
_this.stratumServer.on('started', function(){
|
_this.stratumServer.on('started', function () {
|
||||||
options.initStats.stratumPorts = Object.keys(options.ports);
|
options.initStats.stratumPorts = Object.keys(options.ports);
|
||||||
_this.stratumServer.broadcastMiningJobs(_this.jobManager.currentJob.getJobParams());
|
_this.stratumServer.broadcastMiningJobs(_this.jobManager.currentJob.getJobParams());
|
||||||
finishedCallback();
|
finishedCallback();
|
||||||
|
|
||||||
}).on('broadcastTimeout', function(){
|
}).on('broadcastTimeout', function () {
|
||||||
emitLog('No new blocks for ' + options.jobRebroadcastTimeout + ' seconds - updating transactions & rebroadcasting work');
|
emitLog('No new blocks for ' + options.jobRebroadcastTimeout + ' seconds - updating transactions & rebroadcasting work');
|
||||||
|
|
||||||
GetBlockTemplate(function(error, rpcData, processedBlock){
|
GetBlockTemplate(function (error, rpcData, processedBlock) {
|
||||||
if (error || processedBlock) return;
|
if (error || processedBlock) return;
|
||||||
_this.jobManager.updateCurrentJob(rpcData);
|
_this.jobManager.updateCurrentJob(rpcData);
|
||||||
});
|
});
|
||||||
|
|
||||||
}).on('client.connected', function(client){
|
}).on('client.connected', function (client) {
|
||||||
if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') {
|
if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') {
|
||||||
_this.varDiff[client.socket.localPort].manageClient(client);
|
_this.varDiff[client.socket.localPort].manageClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
client.on('difficultyChanged', function(diff){
|
client.on('difficultyChanged', function (diff) {
|
||||||
_this.emit('difficultyUpdate', client.workerName, diff);
|
_this.emit('difficultyUpdate', client.workerName, diff);
|
||||||
|
|
||||||
}).on('subscription', function(params, resultCallback){
|
}).on('subscription', function (params, resultCallback) {
|
||||||
|
|
||||||
var extraNonce = _this.jobManager.extraNonceCounter.next();
|
var extraNonce = _this.jobManager.extraNonceCounter.next();
|
||||||
var extraNonce2Size = _this.jobManager.extraNonce2Size;
|
|
||||||
resultCallback(null,
|
resultCallback(null,
|
||||||
extraNonce,
|
extraNonce,
|
||||||
extraNonce2Size
|
extraNonce
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// FIXME
|
||||||
if (typeof(options.ports[client.socket.localPort]) !== 'undefined' && options.ports[client.socket.localPort].diff) {
|
if (typeof(options.ports[client.socket.localPort]) !== 'undefined' && options.ports[client.socket.localPort].diff) {
|
||||||
this.sendDifficulty(options.ports[client.socket.localPort].diff);
|
this.sendDifficulty(options.ports[client.socket.localPort].diff);
|
||||||
} else {
|
} else {
|
||||||
this.sendDifficulty(8);
|
this.sendDifficulty(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
|
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
|
||||||
|
|
||||||
}).on('submit', function(params, resultCallback){
|
}).on('submit', function (params, resultCallback) {
|
||||||
var result =_this.jobManager.processShare(
|
var result = _this.jobManager.processShare(
|
||||||
params.jobId,
|
params.jobId,
|
||||||
client.previousDifficulty,
|
client.previousDifficulty,
|
||||||
client.difficulty,
|
client.difficulty,
|
||||||
|
@ -502,7 +503,8 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
params.nonce,
|
params.nonce,
|
||||||
client.remoteAddress,
|
client.remoteAddress,
|
||||||
client.socket.localPort,
|
client.socket.localPort,
|
||||||
params.name
|
params.name,
|
||||||
|
params.soln
|
||||||
);
|
);
|
||||||
|
|
||||||
resultCallback(result.error, result.result ? true : null);
|
resultCallback(result.error, result.result ? true : null);
|
||||||
|
@ -510,34 +512,34 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}).on('malformedMessage', function (message) {
|
}).on('malformedMessage', function (message) {
|
||||||
emitWarningLog('Malformed message from ' + client.getLabel() + ': ' + message);
|
emitWarningLog('Malformed message from ' + client.getLabel() + ': ' + message);
|
||||||
|
|
||||||
}).on('socketError', function(err) {
|
}).on('socketError', function (err) {
|
||||||
emitWarningLog('Socket error from ' + client.getLabel() + ': ' + JSON.stringify(err));
|
emitWarningLog('Socket error from ' + client.getLabel() + ': ' + JSON.stringify(err));
|
||||||
|
|
||||||
}).on('socketTimeout', function(reason){
|
}).on('socketTimeout', function (reason) {
|
||||||
emitWarningLog('Connected timed out for ' + client.getLabel() + ': ' + reason)
|
emitWarningLog('Connected timed out for ' + client.getLabel() + ': ' + reason)
|
||||||
|
|
||||||
}).on('socketDisconnect', function() {
|
}).on('socketDisconnect', function () {
|
||||||
//emitLog('Socket disconnected from ' + client.getLabel());
|
//emitLog('Socket disconnected from ' + client.getLabel());
|
||||||
|
|
||||||
}).on('kickedBannedIP', function(remainingBanTime){
|
}).on('kickedBannedIP', function (remainingBanTime) {
|
||||||
emitLog('Rejected incoming connection from ' + client.remoteAddress + ' banned for ' + remainingBanTime + ' more seconds');
|
emitLog('Rejected incoming connection from ' + client.remoteAddress + ' banned for ' + remainingBanTime + ' more seconds');
|
||||||
|
|
||||||
}).on('forgaveBannedIP', function(){
|
}).on('forgaveBannedIP', function () {
|
||||||
emitLog('Forgave banned IP ' + client.remoteAddress);
|
emitLog('Forgave banned IP ' + client.remoteAddress);
|
||||||
|
|
||||||
}).on('unknownStratumMethod', function(fullMessage) {
|
}).on('unknownStratumMethod', function (fullMessage) {
|
||||||
emitLog('Unknown stratum method from ' + client.getLabel() + ': ' + fullMessage.method);
|
emitLog('Unknown stratum method from ' + client.getLabel() + ': ' + fullMessage.method);
|
||||||
|
|
||||||
}).on('socketFlooded', function() {
|
}).on('socketFlooded', function () {
|
||||||
emitWarningLog('Detected socket flooding from ' + client.getLabel());
|
emitWarningLog('Detected socket flooding from ' + client.getLabel());
|
||||||
|
|
||||||
}).on('tcpProxyError', function(data) {
|
}).on('tcpProxyError', function (data) {
|
||||||
emitErrorLog('Client IP detection failed, tcpProxyProtocol is enabled yet did not receive proxy protocol message, instead got data: ' + data);
|
emitErrorLog('Client IP detection failed, tcpProxyProtocol is enabled yet did not receive proxy protocol message, instead got data: ' + data);
|
||||||
|
|
||||||
}).on('bootedBannedWorker', function(){
|
}).on('bootedBannedWorker', function () {
|
||||||
emitWarningLog('Booted worker ' + client.getLabel() + ' who was connected from an IP address that was just banned');
|
emitWarningLog('Booted worker ' + client.getLabel() + ' who was connected from an IP address that was just banned');
|
||||||
|
|
||||||
}).on('triggerBan', function(reason){
|
}).on('triggerBan', function (reason) {
|
||||||
emitWarningLog('Banned triggered for ' + client.getLabel() + ': ' + reason);
|
emitWarningLog('Banned triggered for ' + client.getLabel() + ': ' + reason);
|
||||||
_this.emit('banIP', client.remoteAddress, client.workerName);
|
_this.emit('banIP', client.remoteAddress, client.workerName);
|
||||||
});
|
});
|
||||||
|
@ -545,9 +547,8 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function SetupBlockPolling() {
|
||||||
function SetupBlockPolling(){
|
if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0) {
|
||||||
if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0){
|
|
||||||
emitLog('Block template polling has been disabled');
|
emitLog('Block template polling has been disabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -555,7 +556,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
var pollingInterval = options.blockRefreshInterval;
|
var pollingInterval = options.blockRefreshInterval;
|
||||||
|
|
||||||
blockPollingIntervalId = setInterval(function () {
|
blockPollingIntervalId = setInterval(function () {
|
||||||
GetBlockTemplate(function(error, result, foundNewBlock){
|
GetBlockTemplate(function (error, result, foundNewBlock) {
|
||||||
if (foundNewBlock)
|
if (foundNewBlock)
|
||||||
emitLog('Block notification via RPC polling');
|
emitLog('Block notification via RPC polling');
|
||||||
});
|
});
|
||||||
|
@ -563,56 +564,73 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetBlockTemplate(callback) {
|
||||||
function GetBlockTemplate(callback){
|
|
||||||
_this.daemon.cmd('getblocktemplate',
|
_this.daemon.cmd('getblocktemplate',
|
||||||
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
|
[{"capabilities": ["coinbasetxn", "workid", "coinbase/append"]}],
|
||||||
function(result){
|
function (result) {
|
||||||
if (result.error){
|
if (result.error) {
|
||||||
emitErrorLog('getblocktemplate call failed for daemon instance ' +
|
emitErrorLog('getblocktemplate call failed for daemon instance ' +
|
||||||
result.instance.index + ' with error ' + JSON.stringify(result.error));
|
result.instance.index + ' with error ' + JSON.stringify(result.error));
|
||||||
callback(result.error);
|
callback(result.error);
|
||||||
} else {
|
} else {
|
||||||
var processedNewBlock = _this.jobManager.processTemplate(result.response);
|
var processedNewBlock = _this.jobManager.processTemplate(result.response);
|
||||||
callback(null, result.response, processedNewBlock);
|
callback(null, result.response, processedNewBlock);
|
||||||
callback = function(){};
|
callback = function () {
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}, true
|
}, true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function GetBlockSubsidy(callback) {
|
||||||
function CheckBlockAccepted(blockHash, callback){
|
_this.daemon.cmd('getblocksubsidy',
|
||||||
//setTimeout(function(){
|
[],
|
||||||
_this.daemon.cmd('getblock',
|
function (result) {
|
||||||
[blockHash],
|
if (result.error) {
|
||||||
function(results){
|
emitErrorLog('getblocksubsidy call failed for daemon instance ' +
|
||||||
var validResults = results.filter(function(result){
|
result.instance.index + ' with error ' + JSON.stringify(result.error));
|
||||||
return result.response && (result.response.hash === blockHash)
|
callback(result.error);
|
||||||
});
|
} else {
|
||||||
|
var processedNewBlock = _this.jobManager.processTemplate(result.response);
|
||||||
if (validResults.length >= 1){
|
callback(result.response);
|
||||||
callback(true, validResults[0].response.tx[0]);
|
callback = function () {
|
||||||
}
|
};
|
||||||
else{
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
}, true
|
||||||
//}, 500);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function CheckBlockAccepted(blockHash, callback) {
|
||||||
|
//setTimeout(function(){
|
||||||
|
_this.daemon.cmd('getblock',
|
||||||
|
[blockHash],
|
||||||
|
function (results) {
|
||||||
|
var validResults = results.filter(function (result) {
|
||||||
|
return result.response && (result.response.hash === blockHash)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (validResults.length >= 1) {
|
||||||
|
callback(true, validResults[0].response.tx[0]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
//}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is being called from the blockNotify so that when a new block is discovered by the daemon
|
* 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
|
* We can inform our miners about the newly found block
|
||||||
**/
|
**/
|
||||||
this.processBlockNotify = function(blockHash, sourceTrigger) {
|
this.processBlockNotify = function (blockHash, sourceTrigger) {
|
||||||
emitLog('Block notification via ' + sourceTrigger);
|
emitLog('Block notification via ' + sourceTrigger);
|
||||||
if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
|
if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash) {
|
||||||
GetBlockTemplate(function(error, result){
|
GetBlockTemplate(function (error, result) {
|
||||||
if (error)
|
if (error)
|
||||||
emitErrorLog('Block notify error getting block template for ' + options.coin.name);
|
emitErrorLog('Block notify error getting block template for ' + options.coin.name);
|
||||||
})
|
})
|
||||||
|
@ -620,7 +638,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.relinquishMiners = function(filterFn, resultCback) {
|
this.relinquishMiners = function (filterFn, resultCback) {
|
||||||
var origStratumClients = this.stratumServer.getStratumClients();
|
var origStratumClients = this.stratumServer.getStratumClients();
|
||||||
|
|
||||||
var stratumClients = [];
|
var stratumClients = [];
|
||||||
|
@ -631,7 +649,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
stratumClients,
|
stratumClients,
|
||||||
filterFn,
|
filterFn,
|
||||||
function (clientsToRelinquish) {
|
function (clientsToRelinquish) {
|
||||||
clientsToRelinquish.forEach(function(cObj) {
|
clientsToRelinquish.forEach(function (cObj) {
|
||||||
cObj.client.removeAllListeners();
|
cObj.client.removeAllListeners();
|
||||||
_this.stratumServer.removeStratumClientBySubId(cObj.subId);
|
_this.stratumServer.removeStratumClientBySubId(cObj.subId);
|
||||||
});
|
});
|
||||||
|
@ -650,7 +668,7 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.attachMiners = function(miners) {
|
this.attachMiners = function (miners) {
|
||||||
miners.forEach(function (clientObj) {
|
miners.forEach(function (clientObj) {
|
||||||
_this.stratumServer.manuallyAddStratumClient(clientObj);
|
_this.stratumServer.manuallyAddStratumClient(clientObj);
|
||||||
});
|
});
|
||||||
|
@ -659,32 +677,31 @@ var pool = module.exports = function pool(options, authorizeFn){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.getStratumServer = function() {
|
this.getStratumServer = function () {
|
||||||
return _this.stratumServer;
|
return _this.stratumServer;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.setVarDiff = function(port, varDiffConfig) {
|
this.setVarDiff = function (port, varDiffConfig) {
|
||||||
if (typeof(_this.varDiff[port]) != 'undefined' ) {
|
if (typeof(_this.varDiff[port]) != 'undefined') {
|
||||||
_this.varDiff[port].removeAllListeners();
|
_this.varDiff[port].removeAllListeners();
|
||||||
}
|
}
|
||||||
var varDiffInstance = new varDiff(port, varDiffConfig);
|
_this.varDiff[port] = new varDiff(port, varDiffConfig);
|
||||||
_this.varDiff[port] = varDiffInstance;
|
_this.varDiff[port].on('newDifficulty', function (client, newDiff) {
|
||||||
_this.varDiff[port].on('newDifficulty', function(client, newDiff) {
|
|
||||||
|
|
||||||
/* We request to set the newDiff @ the next difficulty retarget
|
/* We request to set the newDiff @ the next difficulty retarget
|
||||||
(which should happen when a new job comes in - AKA BLOCK) */
|
(which should happen when a new job comes in - AKA BLOCK) */
|
||||||
client.enqueueNextDifficulty(newDiff);
|
client.enqueueNextDifficulty(newDiff);
|
||||||
|
|
||||||
/*if (options.varDiff.mode === 'fast'){
|
/*if (options.varDiff.mode === 'fast'){
|
||||||
//Send new difficulty, then force miner to use new diff by resending the
|
//Send new difficulty, then force miner to use new diff by resending the
|
||||||
//current job parameters but with the "clean jobs" flag set to false
|
//current job parameters but with the "clean jobs" flag set to false
|
||||||
//so the miner doesn't restart work and submit duplicate shares
|
//so the miner doesn't restart work and submit duplicate shares
|
||||||
client.sendDifficulty(newDiff);
|
client.sendDifficulty(newDiff);
|
||||||
var job = _this.jobManager.currentJob.getJobParams();
|
var job = _this.jobManager.currentJob.getJobParams();
|
||||||
job[8] = false;
|
job[8] = false;
|
||||||
client.sendMiningJob(job);
|
client.sendMiningJob(job);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,17 +27,13 @@ var StratumClient = function(options){
|
||||||
var pendingDifficulty = null;
|
var pendingDifficulty = null;
|
||||||
//private members
|
//private members
|
||||||
this.socket = options.socket;
|
this.socket = options.socket;
|
||||||
|
|
||||||
this.remoteAddress = options.socket.remoteAddress;
|
this.remoteAddress = options.socket.remoteAddress;
|
||||||
|
|
||||||
var banning = options.banning;
|
var banning = options.banning;
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
this.lastActivity = Date.now();
|
this.lastActivity = Date.now();
|
||||||
|
|
||||||
this.shares = {valid: 0, invalid: 0};
|
this.shares = {valid: 0, invalid: 0};
|
||||||
|
|
||||||
|
|
||||||
var considerBan = (!banning || !banning.enabled) ? function(){ return false } : function(shareValid){
|
var considerBan = (!banning || !banning.enabled) ? function(){ return false } : function(shareValid){
|
||||||
if (shareValid === true) _this.shares.valid++;
|
if (shareValid === true) _this.shares.valid++;
|
||||||
else _this.shares.invalid++;
|
else _this.shares.invalid++;
|
||||||
|
@ -65,7 +61,7 @@ var StratumClient = function(options){
|
||||||
handleSubscribe(message);
|
handleSubscribe(message);
|
||||||
break;
|
break;
|
||||||
case 'mining.authorize':
|
case 'mining.authorize':
|
||||||
handleAuthorize(message, true /*reply to socket*/);
|
handleAuthorize(message);
|
||||||
break;
|
break;
|
||||||
case 'mining.submit':
|
case 'mining.submit':
|
||||||
_this.lastActivity = Date.now();
|
_this.lastActivity = Date.now();
|
||||||
|
@ -78,6 +74,13 @@ var StratumClient = function(options){
|
||||||
error : true
|
error : true
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'mining.extranonce.subscribe':
|
||||||
|
sendJson({
|
||||||
|
id: message.id,
|
||||||
|
result: false,
|
||||||
|
error: [20, "Not supported.", null]
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
_this.emit('unknownStratumMethod', message);
|
_this.emit('unknownStratumMethod', message);
|
||||||
break;
|
break;
|
||||||
|
@ -85,12 +88,13 @@ var StratumClient = function(options){
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubscribe(message){
|
function handleSubscribe(message){
|
||||||
if (! _this._authorized ) {
|
if (!_this.authorized) {
|
||||||
_this.requestedSubscriptionBeforeAuth = true;
|
_this.requestedSubscriptionBeforeAuth = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_this.emit('subscription',
|
_this.emit('subscription',
|
||||||
{},
|
{},
|
||||||
function(error, extraNonce1, extraNonce2Size){
|
function(error, extraNonce1, extraNonce1){
|
||||||
if (error){
|
if (error){
|
||||||
sendJson({
|
sendJson({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
|
@ -100,35 +104,30 @@ var StratumClient = function(options){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_this.extraNonce1 = extraNonce1;
|
_this.extraNonce1 = extraNonce1;
|
||||||
|
|
||||||
sendJson({
|
sendJson({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
result: [
|
result: [
|
||||||
[
|
null, //sessionId
|
||||||
["mining.set_difficulty", options.subscriptionId],
|
extraNonce1
|
||||||
["mining.notify", options.subscriptionId]
|
|
||||||
],
|
|
||||||
extraNonce1,
|
|
||||||
extraNonce2Size
|
|
||||||
],
|
],
|
||||||
error: null
|
error: null
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAuthorize(message, replyToSocket){
|
function handleAuthorize(message){
|
||||||
_this.workerName = message.params[0];
|
_this.workerName = message.params[0];
|
||||||
_this.workerPass = message.params[1];
|
_this.workerPass = message.params[1];
|
||||||
|
|
||||||
options.authorizeFn(_this.remoteAddress, options.socket.localPort, _this.workerName, _this.workerPass, function(result) {
|
options.authorizeFn(_this.remoteAddress, options.socket.localPort, _this.workerName, _this.workerPass, function(result) {
|
||||||
_this.authorized = (!result.error && result.authorized);
|
_this.authorized = (!result.error && result.authorized);
|
||||||
|
|
||||||
if (replyToSocket) {
|
sendJson({
|
||||||
sendJson({
|
id : message.id,
|
||||||
id : message.id,
|
result : _this.authorized,
|
||||||
result : _this.authorized,
|
error : result.error
|
||||||
error : result.error
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the authorizer wants us to close the socket lets do it.
|
// If the authorizer wants us to close the socket lets do it.
|
||||||
if (result.disconnect === true) {
|
if (result.disconnect === true) {
|
||||||
|
@ -138,7 +137,7 @@ var StratumClient = function(options){
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(message){
|
function handleSubmit(message){
|
||||||
if (!_this.authorized){
|
if (_this.authorized === false){
|
||||||
sendJson({
|
sendJson({
|
||||||
id : message.id,
|
id : message.id,
|
||||||
result: null,
|
result: null,
|
||||||
|
@ -160,9 +159,10 @@ var StratumClient = function(options){
|
||||||
{
|
{
|
||||||
name : message.params[0],
|
name : message.params[0],
|
||||||
jobId : message.params[1],
|
jobId : message.params[1],
|
||||||
extraNonce2 : message.params[2],
|
nTime : message.params[2],
|
||||||
nTime : message.params[3],
|
extraNonce2 : message.params[3],
|
||||||
nonce : message.params[4]
|
soln : message.params[4],
|
||||||
|
nonce : _this.extraNonce1 + message.params[3]
|
||||||
},
|
},
|
||||||
function(error, result){
|
function(error, result){
|
||||||
if (!considerBan(result)){
|
if (!considerBan(result)){
|
||||||
|
@ -216,7 +216,7 @@ var StratumClient = function(options){
|
||||||
var messages = dataBuffer.split('\n');
|
var messages = dataBuffer.split('\n');
|
||||||
var incomplete = dataBuffer.slice(-1) === '\n' ? '' : messages.pop();
|
var incomplete = dataBuffer.slice(-1) === '\n' ? '' : messages.pop();
|
||||||
messages.forEach(function(message){
|
messages.forEach(function(message){
|
||||||
if (message === '') return;
|
if (message.length < 1) return;
|
||||||
var messageJson;
|
var messageJson;
|
||||||
try {
|
try {
|
||||||
messageJson = JSON.parse(message);
|
messageJson = JSON.parse(message);
|
||||||
|
@ -225,9 +225,9 @@ var StratumClient = function(options){
|
||||||
_this.emit('malformedMessage', message);
|
_this.emit('malformedMessage', message);
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageJson) {
|
if (messageJson) {
|
||||||
handleMessage(messageJson);
|
handleMessage(messageJson);
|
||||||
}
|
}
|
||||||
|
@ -260,16 +260,12 @@ var StratumClient = function(options){
|
||||||
* IF the given difficulty is valid and new it'll send it to the client.
|
* IF the given difficulty is valid and new it'll send it to the client.
|
||||||
* returns boolean
|
* returns boolean
|
||||||
**/
|
**/
|
||||||
this.sendDifficulty = function(difficulty){
|
this.sendDifficulty = function(target){
|
||||||
if (difficulty === this.difficulty)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_this.previousDifficulty = _this.difficulty;
|
|
||||||
_this.difficulty = difficulty;
|
|
||||||
sendJson({
|
sendJson({
|
||||||
id : null,
|
id : null,
|
||||||
method: "mining.set_difficulty",
|
method: "mining.set_target",
|
||||||
params: [difficulty]//[512],
|
params: ["0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"] // FIXME
|
||||||
|
//params: ["00f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0"] // suprnova
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -278,18 +274,10 @@ var StratumClient = function(options){
|
||||||
|
|
||||||
var lastActivityAgo = Date.now() - _this.lastActivity;
|
var lastActivityAgo = Date.now() - _this.lastActivity;
|
||||||
if (lastActivityAgo > options.connectionTimeout * 1000){
|
if (lastActivityAgo > options.connectionTimeout * 1000){
|
||||||
_this.emit('socketTimeout', 'last submitted a share was ' + (lastActivityAgo / 1000 | 0) + ' seconds ago');
|
|
||||||
_this.socket.destroy();
|
_this.socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pendingDifficulty !== null){
|
|
||||||
var result = _this.sendDifficulty(pendingDifficulty);
|
|
||||||
pendingDifficulty = null;
|
|
||||||
if (result) {
|
|
||||||
_this.emit('difficultyChanged', _this.difficulty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendJson({
|
sendJson({
|
||||||
id : null,
|
id : null,
|
||||||
method: "mining.notify",
|
method: "mining.notify",
|
||||||
|
@ -358,7 +346,7 @@ var StratumServer = exports.Server = function StratumServer(options, authorizeFn
|
||||||
var client = new StratumClient(
|
var client = new StratumClient(
|
||||||
{
|
{
|
||||||
subscriptionId: subscriptionId,
|
subscriptionId: subscriptionId,
|
||||||
authorizeFn: authorizeFn,
|
authorizeFn: authorizeFn, //FIXME
|
||||||
socket: socket,
|
socket: socket,
|
||||||
banning: options.banning,
|
banning: options.banning,
|
||||||
connectionTimeout: options.connectionTimeout,
|
connectionTimeout: options.connectionTimeout,
|
||||||
|
|
|
@ -1,245 +1,86 @@
|
||||||
|
var bitcoin = require('bitcoinjs-lib');
|
||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
|
// public members
|
||||||
|
var txHash;
|
||||||
|
|
||||||
/*
|
exports.txHash = function(){
|
||||||
function Transaction(params){
|
return txHash;
|
||||||
|
|
||||||
var version = params.version || 1,
|
|
||||||
inputs = params.inputs || [],
|
|
||||||
outputs = params.outputs || [],
|
|
||||||
lockTime = params.lockTime || 0;
|
|
||||||
|
|
||||||
|
|
||||||
this.toBuffer = function(){
|
|
||||||
return Buffer.concat([
|
|
||||||
binpack.packUInt32(version, 'little'),
|
|
||||||
util.varIntBuffer(inputs.length),
|
|
||||||
Buffer.concat(inputs.map(function(i){ return i.toBuffer() })),
|
|
||||||
util.varIntBuffer(outputs.length),
|
|
||||||
Buffer.concat(outputs.map(function(o){ return o.toBuffer() })),
|
|
||||||
binpack.packUInt32(lockTime, 'little')
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.inputs = inputs;
|
|
||||||
this.outputs = outputs;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function TransactionInput(params){
|
|
||||||
|
|
||||||
var prevOutHash = params.prevOutHash || 0,
|
|
||||||
prevOutIndex = params.prevOutIndex,
|
|
||||||
sigScript = params.sigScript,
|
|
||||||
sequence = params.sequence || 0;
|
|
||||||
|
|
||||||
|
|
||||||
this.toBuffer = function(){
|
|
||||||
sigScriptBuffer = sigScript.toBuffer();
|
|
||||||
console.log('scriptSig length ' + sigScriptBuffer.length);
|
|
||||||
return Buffer.concat([
|
|
||||||
util.uint256BufferFromHash(prevOutHash),
|
|
||||||
binpack.packUInt32(prevOutIndex, 'little'),
|
|
||||||
util.varIntBuffer(sigScriptBuffer.length),
|
|
||||||
sigScriptBuffer,
|
|
||||||
binpack.packUInt32(sequence)
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function TransactionOutput(params){
|
|
||||||
|
|
||||||
var value = params.value,
|
|
||||||
pkScriptBuffer = params.pkScriptBuffer;
|
|
||||||
|
|
||||||
this.toBuffer = function(){
|
|
||||||
return Buffer.concat([
|
|
||||||
binpack.packInt64(value, 'little'),
|
|
||||||
util.varIntBuffer(pkScriptBuffer.length),
|
|
||||||
pkScriptBuffer
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function ScriptSig(params){
|
|
||||||
|
|
||||||
var height = params.height,
|
|
||||||
flags = params.flags,
|
|
||||||
extraNoncePlaceholder = params.extraNoncePlaceholder;
|
|
||||||
|
|
||||||
this.toBuffer = function(){
|
|
||||||
|
|
||||||
return Buffer.concat([
|
|
||||||
util.serializeNumber(height),
|
|
||||||
new Buffer(flags, 'hex'),
|
|
||||||
util.serializeNumber(Date.now() / 1000 | 0),
|
|
||||||
new Buffer([extraNoncePlaceholder.length]),
|
|
||||||
extraNoncePlaceholder,
|
|
||||||
util.serializeString('/nodeStratum/')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var Generation = exports.Generation = function Generation(rpcData, publicKey, extraNoncePlaceholder){
|
function scriptCompile(pubkey){
|
||||||
|
script = bitcoin.script.compile(
|
||||||
|
[
|
||||||
|
bitcoin.opcodes.OP_DUP,
|
||||||
|
bitcoin.opcodes.OP_HASH160,
|
||||||
|
pubkey,
|
||||||
|
bitcoin.opcodes.OP_EQUALVERIFY,
|
||||||
|
bitcoin.opcodes.OP_CHECKSIG
|
||||||
|
]);
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
var tx = new Transaction({
|
exports.createGeneration = function(blockHeight, reward, recipients, poolAddress){
|
||||||
inputs: [new TransactionInput({
|
var tx = new bitcoin.Transaction();
|
||||||
prevOutIndex : Math.pow(2, 32) - 1,
|
|
||||||
sigScript : new ScriptSig({
|
// input for coinbase tx
|
||||||
height : rpcData.height,
|
if (blockHeight.toString(16).length % 2 === 0) {
|
||||||
flags : rpcData.coinbaseaux.flags,
|
var blockHeightSerial = blockHeight.toString(16);
|
||||||
extraNoncePlaceholder : extraNoncePlaceholder
|
} else {
|
||||||
})
|
var blockHeightSerial = '0' + blockHeight.toString(16);
|
||||||
})],
|
}
|
||||||
outputs: [new TransactionOutput({
|
length = '0' + (blockHeightSerial.length / 2);
|
||||||
value : rpcData.coinbasevalue,
|
var serializedBlockHeight = Buffer.concat([
|
||||||
pkScriptBuffer : publicKey
|
Buffer(length, 'hex'),
|
||||||
})]
|
util.reverseBuffer(Buffer(blockHeightSerial, 'hex'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
tx.addInput(new Buffer('0000000000000000000000000000000000000000000000000000000000000000', 'hex'),
|
||||||
|
4294967295,
|
||||||
|
4294967295,
|
||||||
|
Buffer.concat([serializedBlockHeight,
|
||||||
|
Buffer('5a2d4e4f4d5021', 'hex')]) // Z-NOMP!
|
||||||
|
);
|
||||||
|
|
||||||
|
// calculate total fees
|
||||||
|
var feePercent = 0;
|
||||||
|
recipients.forEach(function(value) {
|
||||||
|
feePercent = feePercent + recipients[value.fee];
|
||||||
});
|
});
|
||||||
|
|
||||||
var txBuffer = tx.toBuffer();
|
// tx for mining pool
|
||||||
var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder);
|
tx.addOutput(
|
||||||
var p1 = txBuffer.slice(0, epIndex);
|
scriptCompile(bitcoin.address.fromBase58Check(poolAddress).hash),
|
||||||
var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length);
|
reward * (1 - feePercent)
|
||||||
|
);
|
||||||
|
|
||||||
this.transaction = tx;
|
// tx for recipients
|
||||||
this.coinbase = [p1, p2];
|
recipients.forEach(function(value){
|
||||||
|
tx.addOutput(
|
||||||
|
recipients[value],
|
||||||
|
reward * recipients[value.fee]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
};
|
// sign with random key
|
||||||
*/
|
txHex = tx.toHex();
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
^^^^ The above code was a bit slow. The below code is uglier but optimized.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
This function creates the generation transaction that accepts the reward for
|
|
||||||
successfully mining a new block.
|
|
||||||
For some (probably outdated and incorrect) documentation about whats kinda going on here,
|
|
||||||
see: https://en.bitcoin.it/wiki/Protocol_specification#tx
|
|
||||||
*/
|
|
||||||
|
|
||||||
var generateOutputTransactions = function(poolRecipient, recipients, rpcData){
|
|
||||||
|
|
||||||
var reward = rpcData.coinbasevalue;
|
|
||||||
var rewardToPool = reward;
|
|
||||||
|
|
||||||
var txOutputBuffers = [];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (rpcData.payee) {
|
|
||||||
var payeeReward = Math.ceil(reward / 5);
|
|
||||||
|
|
||||||
reward -= payeeReward;
|
|
||||||
rewardToPool -= payeeReward;
|
|
||||||
|
|
||||||
var payeeScript = util.addressToScript(rpcData.payee);
|
|
||||||
txOutputBuffers.push(Buffer.concat([
|
|
||||||
util.packInt64LE(payeeReward),
|
|
||||||
util.varIntBuffer(payeeScript.length),
|
|
||||||
payeeScript
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (var i = 0; i < recipients.length; i++){
|
|
||||||
var recipientReward = Math.floor(recipients[i].percent * reward);
|
|
||||||
rewardToPool -= recipientReward;
|
|
||||||
|
|
||||||
txOutputBuffers.push(Buffer.concat([
|
|
||||||
util.packInt64LE(recipientReward),
|
|
||||||
util.varIntBuffer(recipients[i].script.length),
|
|
||||||
recipients[i].script
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
txOutputBuffers.unshift(Buffer.concat([
|
|
||||||
util.packInt64LE(rewardToPool),
|
|
||||||
util.varIntBuffer(poolRecipient.length),
|
|
||||||
poolRecipient
|
|
||||||
]));
|
|
||||||
|
|
||||||
|
|
||||||
return Buffer.concat([
|
|
||||||
util.varIntBuffer(txOutputBuffers.length),
|
|
||||||
Buffer.concat(txOutputBuffers)
|
|
||||||
]);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
exports.CreateGeneration = function(rpcData, publicKey, extraNoncePlaceholder, reward, txMessages, recipients){
|
|
||||||
|
|
||||||
var txInputsCount = 1;
|
|
||||||
var txOutputsCount = 1;
|
|
||||||
var txVersion = txMessages === true ? 2 : 1;
|
|
||||||
var txLockTime = 0;
|
|
||||||
|
|
||||||
var txInPrevOutHash = 0;
|
|
||||||
var txInPrevOutIndex = Math.pow(2, 32) - 1;
|
|
||||||
var txInSequence = 0;
|
|
||||||
|
|
||||||
//Only required for POS coins
|
|
||||||
var txTimestamp = reward === 'POS' ?
|
|
||||||
util.packUInt32LE(rpcData.curtime) : new Buffer([]);
|
|
||||||
|
|
||||||
//For coins that support/require transaction comments
|
|
||||||
var txComment = txMessages === true ?
|
|
||||||
util.serializeString('https://github.com/zone117x/node-stratum') :
|
|
||||||
new Buffer([]);
|
|
||||||
|
|
||||||
|
|
||||||
var scriptSigPart1 = Buffer.concat([
|
|
||||||
util.serializeNumber(rpcData.height),
|
|
||||||
new Buffer(rpcData.coinbaseaux.flags, 'hex'),
|
|
||||||
util.serializeNumber(Date.now() / 1000 | 0),
|
|
||||||
new Buffer([extraNoncePlaceholder.length])
|
|
||||||
]);
|
|
||||||
|
|
||||||
var scriptSigPart2 = util.serializeString('/nodeStratum/');
|
|
||||||
|
|
||||||
var p1 = Buffer.concat([
|
|
||||||
util.packUInt32LE(txVersion),
|
|
||||||
txTimestamp,
|
|
||||||
|
|
||||||
//transaction input
|
|
||||||
util.varIntBuffer(txInputsCount),
|
|
||||||
util.uint256BufferFromHash(txInPrevOutHash),
|
|
||||||
util.packUInt32LE(txInPrevOutIndex),
|
|
||||||
util.varIntBuffer(scriptSigPart1.length + extraNoncePlaceholder.length + scriptSigPart2.length),
|
|
||||||
scriptSigPart1
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
// assign
|
||||||
|
txHash = tx.getHash().toString('hex');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The generation transaction must be split at the extranonce (which located in the transaction input
|
console.log('txHex: ' + txHex.toString('hex'));
|
||||||
scriptSig). Miners send us unique extranonces that we use to join the two parts in attempt to create
|
console.log('txHash: ' + txHash);
|
||||||
a valid share and/or block.
|
*/
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
var outputTransactions = generateOutputTransactions(publicKey, recipients, rpcData);
|
|
||||||
|
|
||||||
var p2 = Buffer.concat([
|
|
||||||
scriptSigPart2,
|
|
||||||
util.packUInt32LE(txInSequence),
|
|
||||||
//end transaction input
|
|
||||||
|
|
||||||
//transaction output
|
|
||||||
outputTransactions,
|
|
||||||
//end transaction ouput
|
|
||||||
|
|
||||||
util.packUInt32LE(txLockTime),
|
|
||||||
txComment
|
|
||||||
]);
|
|
||||||
|
|
||||||
return [p1, p2];
|
|
||||||
|
|
||||||
|
return txHex;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.getFees = function(feeArray){
|
||||||
|
var fee = Number();
|
||||||
|
feeArray.forEach(function(value) {
|
||||||
|
fee = Math.abs(fee) + Number(value.fee);
|
||||||
|
});
|
||||||
|
return fee;
|
||||||
};
|
};
|
||||||
|
|
157
lib/util.js
157
lib/util.js
|
@ -4,7 +4,7 @@ var base58 = require('base58-native');
|
||||||
var bignum = require('bignum');
|
var bignum = require('bignum');
|
||||||
|
|
||||||
|
|
||||||
exports.addressFromEx = function(exAddress, ripdm160Key){
|
exports.addressFromEx = function (exAddress, ripdm160Key) {
|
||||||
try {
|
try {
|
||||||
var versionByte = exports.getVersionByte(exAddress);
|
var versionByte = exports.getVersionByte(exAddress);
|
||||||
var addrBase = Buffer.concat([versionByte, new Buffer(ripdm160Key, 'hex')]);
|
var addrBase = Buffer.concat([versionByte, new Buffer(ripdm160Key, 'hex')]);
|
||||||
|
@ -12,48 +12,48 @@ exports.addressFromEx = function(exAddress, ripdm160Key){
|
||||||
var address = Buffer.concat([addrBase, checksum]);
|
var address = Buffer.concat([addrBase, checksum]);
|
||||||
return base58.encode(address);
|
return base58.encode(address);
|
||||||
}
|
}
|
||||||
catch(e){
|
catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.getVersionByte = function(addr){
|
exports.getVersionByte = function (addr) {
|
||||||
var versionByte = base58.decode(addr).slice(0, 1);
|
var versionByte = base58.decode(addr).slice(0, 1);
|
||||||
return versionByte;
|
return versionByte;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sha256 = function(buffer){
|
exports.sha256 = function (buffer) {
|
||||||
var hash1 = crypto.createHash('sha256');
|
var hash1 = crypto.createHash('sha256');
|
||||||
hash1.update(buffer);
|
hash1.update(buffer);
|
||||||
return hash1.digest();
|
return hash1.digest();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sha256d = function(buffer){
|
exports.sha256d = function (buffer) {
|
||||||
return exports.sha256(exports.sha256(buffer));
|
return exports.sha256(exports.sha256(buffer));
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.reverseBuffer = function(buff){
|
exports.reverseBuffer = function (buff) {
|
||||||
var reversed = new Buffer(buff.length);
|
var reversed = new Buffer(buff.length);
|
||||||
for (var i = buff.length - 1; i >= 0; i--)
|
for (var i = buff.length - 1; i >= 0; i--)
|
||||||
reversed[buff.length - i - 1] = buff[i];
|
reversed[buff.length - i - 1] = buff[i];
|
||||||
return reversed;
|
return reversed;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.reverseHex = function(hex){
|
exports.reverseHex = function (hex) {
|
||||||
return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex');
|
return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.reverseByteOrder = function(buff){
|
exports.reverseByteOrder = function (buff) {
|
||||||
for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4);
|
for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4);
|
||||||
return exports.reverseBuffer(buff);
|
return exports.reverseBuffer(buff);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.uint256BufferFromHash = function(hex){
|
exports.uint256BufferFromHash = function (hex) {
|
||||||
|
|
||||||
var fromHex = new Buffer(hex, 'hex');
|
var fromHex = new Buffer(hex, 'hex');
|
||||||
|
|
||||||
if (fromHex.length != 32){
|
if (fromHex.length != 32) {
|
||||||
var empty = new Buffer(32);
|
var empty = new Buffer(32);
|
||||||
empty.fill(0);
|
empty.fill(0);
|
||||||
fromHex.copy(empty);
|
fromHex.copy(empty);
|
||||||
|
@ -63,31 +63,31 @@ exports.uint256BufferFromHash = function(hex){
|
||||||
return exports.reverseBuffer(fromHex);
|
return exports.reverseBuffer(fromHex);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.hexFromReversedBuffer = function(buffer){
|
exports.hexFromReversedBuffer = function (buffer) {
|
||||||
return exports.reverseBuffer(buffer).toString('hex');
|
return exports.reverseBuffer(buffer).toString('hex');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Defined in bitcoin protocol here:
|
Defined in bitcoin protocol here:
|
||||||
https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
|
https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer
|
||||||
*/
|
*/
|
||||||
exports.varIntBuffer = function(n){
|
exports.varIntBuffer = function (n) {
|
||||||
if (n < 0xfd)
|
if (n < 0xfd)
|
||||||
return new Buffer([n]);
|
return new Buffer([n]);
|
||||||
else if (n < 0xffff){
|
else if (n < 0xffff) {
|
||||||
var buff = new Buffer(3);
|
var buff = new Buffer(3);
|
||||||
buff[0] = 0xfd;
|
buff[0] = 0xfd;
|
||||||
buff.writeUInt16LE(n, 1);
|
buff.writeUInt16LE(n, 1);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
else if (n < 0xffffffff){
|
else if (n < 0xffffffff) {
|
||||||
var buff = new Buffer(5);
|
var buff = new Buffer(5);
|
||||||
buff[0] = 0xfe;
|
buff[0] = 0xfe;
|
||||||
buff.writeUInt32LE(n, 1);
|
buff.writeUInt32LE(n, 1);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
var buff = new Buffer(9);
|
var buff = new Buffer(9);
|
||||||
buff[0] = 0xff;
|
buff[0] = 0xff;
|
||||||
exports.packUInt16LE(n).copy(buff, 1);
|
exports.packUInt16LE(n).copy(buff, 1);
|
||||||
|
@ -95,48 +95,47 @@ exports.varIntBuffer = function(n){
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.varStringBuffer = function(string){
|
exports.varStringBuffer = function (string) {
|
||||||
var strBuff = new Buffer(string);
|
var strBuff = new Buffer(string);
|
||||||
return Buffer.concat([exports.varIntBuffer(strBuff.length), strBuff]);
|
return Buffer.concat([exports.varIntBuffer(strBuff.length), strBuff]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
"serialized CScript" formatting as defined here:
|
"serialized CScript" formatting as defined here:
|
||||||
https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki#specification
|
https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki#specification
|
||||||
Used to format height and date when putting into script signature:
|
Used to format height and date when putting into script signature:
|
||||||
https://en.bitcoin.it/wiki/Script
|
https://en.bitcoin.it/wiki/Script
|
||||||
*/
|
*/
|
||||||
exports.serializeNumber = function(n){
|
exports.serializeNumber = function (n) {
|
||||||
|
|
||||||
/* Old version that is bugged
|
/* Old version that is bugged
|
||||||
if (n < 0xfd){
|
if (n < 0xfd){
|
||||||
var buff = new Buffer(2);
|
var buff = new Buffer(2);
|
||||||
buff[0] = 0x1;
|
buff[0] = 0x1;
|
||||||
buff.writeUInt8(n, 1);
|
buff.writeUInt8(n, 1);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
else if (n <= 0xffff){
|
else if (n <= 0xffff){
|
||||||
var buff = new Buffer(4);
|
var buff = new Buffer(4);
|
||||||
buff[0] = 0x3;
|
buff[0] = 0x3;
|
||||||
buff.writeUInt16LE(n, 1);
|
buff.writeUInt16LE(n, 1);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
else if (n <= 0xffffffff){
|
else if (n <= 0xffffffff){
|
||||||
var buff = new Buffer(5);
|
var buff = new Buffer(5);
|
||||||
buff[0] = 0x4;
|
buff[0] = 0x4;
|
||||||
buff.writeUInt32LE(n, 1);
|
buff.writeUInt32LE(n, 1);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]);
|
return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
//New version from TheSeven
|
//New version from TheSeven
|
||||||
if (n >= 1 && n <= 16) return new Buffer([0x50 + n]);
|
if (n >= 1 && n <= 16) return new Buffer([0x50 + n]);
|
||||||
var l = 1;
|
var l = 1;
|
||||||
var buff = new Buffer(9);
|
var buff = new Buffer(9);
|
||||||
while (n > 0x7f)
|
while (n > 0x7f) {
|
||||||
{
|
|
||||||
buff.writeUInt8(n & 0xff, l++);
|
buff.writeUInt8(n & 0xff, l++);
|
||||||
n >>= 8;
|
n >>= 8;
|
||||||
}
|
}
|
||||||
|
@ -148,9 +147,9 @@ exports.serializeNumber = function(n){
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Used for serializing strings used in script signature
|
Used for serializing strings used in script signature
|
||||||
*/
|
*/
|
||||||
exports.serializeString = function(s){
|
exports.serializeString = function (s) {
|
||||||
|
|
||||||
if (s.length < 253)
|
if (s.length < 253)
|
||||||
return Buffer.concat([
|
return Buffer.concat([
|
||||||
|
@ -178,33 +177,32 @@ exports.serializeString = function(s){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
exports.packUInt16LE = function (num) {
|
||||||
exports.packUInt16LE = function(num){
|
|
||||||
var buff = new Buffer(2);
|
var buff = new Buffer(2);
|
||||||
buff.writeUInt16LE(num, 0);
|
buff.writeUInt16LE(num, 0);
|
||||||
return buff;
|
return buff;
|
||||||
};
|
};
|
||||||
exports.packInt32LE = function(num){
|
exports.packInt32LE = function (num) {
|
||||||
var buff = new Buffer(4);
|
var buff = new Buffer(4);
|
||||||
buff.writeInt32LE(num, 0);
|
buff.writeInt32LE(num, 0);
|
||||||
return buff;
|
return buff;
|
||||||
};
|
};
|
||||||
exports.packInt32BE = function(num){
|
exports.packInt32BE = function (num) {
|
||||||
var buff = new Buffer(4);
|
var buff = new Buffer(4);
|
||||||
buff.writeInt32BE(num, 0);
|
buff.writeInt32BE(num, 0);
|
||||||
return buff;
|
return buff;
|
||||||
};
|
};
|
||||||
exports.packUInt32LE = function(num){
|
exports.packUInt32LE = function (num) {
|
||||||
var buff = new Buffer(4);
|
var buff = new Buffer(4);
|
||||||
buff.writeUInt32LE(num, 0);
|
buff.writeUInt32LE(num, 0);
|
||||||
return buff;
|
return buff;
|
||||||
};
|
};
|
||||||
exports.packUInt32BE = function(num){
|
exports.packUInt32BE = function (num) {
|
||||||
var buff = new Buffer(4);
|
var buff = new Buffer(4);
|
||||||
buff.writeUInt32BE(num, 0);
|
buff.writeUInt32BE(num, 0);
|
||||||
return buff;
|
return buff;
|
||||||
};
|
};
|
||||||
exports.packInt64LE = function(num){
|
exports.packInt64LE = function (num) {
|
||||||
var buff = new Buffer(8);
|
var buff = new Buffer(8);
|
||||||
buff.writeUInt32LE(num % Math.pow(2, 32), 0);
|
buff.writeUInt32LE(num % Math.pow(2, 32), 0);
|
||||||
buff.writeUInt32LE(Math.floor(num / Math.pow(2, 32)), 4);
|
buff.writeUInt32LE(Math.floor(num / Math.pow(2, 32)), 4);
|
||||||
|
@ -213,34 +211,32 @@ exports.packInt64LE = function(num){
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
An exact copy of python's range feature. Written by Tadeck:
|
An exact copy of python's range feature. Written by Tadeck:
|
||||||
http://stackoverflow.com/a/8273091
|
http://stackoverflow.com/a/8273091
|
||||||
*/
|
*/
|
||||||
exports.range = function(start, stop, step){
|
exports.range = function (start, stop, step) {
|
||||||
if (typeof stop === 'undefined'){
|
if (typeof stop === 'undefined') {
|
||||||
stop = start;
|
stop = start;
|
||||||
start = 0;
|
start = 0;
|
||||||
}
|
}
|
||||||
if (typeof step === 'undefined'){
|
if (typeof step === 'undefined') {
|
||||||
step = 1;
|
step = 1;
|
||||||
}
|
}
|
||||||
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){
|
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
var result = [];
|
var result = [];
|
||||||
for (var i = start; step > 0 ? i < stop : i > stop; i += step){
|
for (var i = start; step > 0 ? i < stop : i > stop; i += step) {
|
||||||
result.push(i);
|
result.push(i);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
For POS coins - used to format wallet address for use in generation transaction's output
|
For POS coins - used to format wallet address for use in generation transaction's output
|
||||||
*/
|
*/
|
||||||
exports.pubkeyToScript = function(key){
|
exports.pubkeyToScript = function (key) {
|
||||||
if (key.length !== 66) {
|
if (key.length !== 66) {
|
||||||
console.error('Invalid pubkey: ' + key);
|
console.error('Invalid pubkey: ' + key);
|
||||||
throw new Error();
|
throw new Error();
|
||||||
|
@ -253,37 +249,37 @@ exports.pubkeyToScript = function(key){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.miningKeyToScript = function(key){
|
exports.miningKeyToScript = function (key) {
|
||||||
var keyBuffer = new Buffer(key, 'hex');
|
var keyBuffer = new Buffer(key, 'hex');
|
||||||
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), keyBuffer, new Buffer([0x88, 0xac])]);
|
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), keyBuffer, new Buffer([0x88, 0xac])]);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
For POW coins - used to format wallet address for use in generation transaction's output
|
For POW coins - used to format wallet address for use in generation transaction's output
|
||||||
*/
|
*/
|
||||||
exports.addressToScript = function(addr){
|
exports.addressToScript = function (addr) {
|
||||||
|
|
||||||
var decoded = base58.decode(addr);
|
var decoded = base58.decode(addr);
|
||||||
|
|
||||||
if (decoded.length != 25){
|
if (decoded.length !== 25 && decoded.length !== 26) {
|
||||||
console.error('invalid address length for ' + addr);
|
console.error('invalid address length for ' + addr);
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!decoded){
|
if (!decoded) {
|
||||||
console.error('base58 decode failed for ' + addr);
|
console.error('base58 decode failed for ' + addr);
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
var pubkey = decoded.slice(1,-4);
|
var pubkey = decoded.slice(1, -4);
|
||||||
|
|
||||||
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkey, new Buffer([0x88, 0xac])]);
|
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkey, new Buffer([0x88, 0xac])]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.getReadableHashRateString = function(hashrate){
|
exports.getReadableHashRateString = function (hashrate) {
|
||||||
var i = -1;
|
var i = -1;
|
||||||
var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ];
|
var byteUnits = [' KH', ' MH', ' GH', ' TH', ' PH'];
|
||||||
do {
|
do {
|
||||||
hashrate = hashrate / 1024;
|
hashrate = hashrate / 1024;
|
||||||
i++;
|
i++;
|
||||||
|
@ -292,10 +288,8 @@ exports.getReadableHashRateString = function(hashrate){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Creates a non-truncated max difficulty (diff1) by bitwise right-shifting the max value of a uint256
|
//Creates a non-truncated max difficulty (diff1) by bitwise right-shifting the max value of a uint256
|
||||||
exports.shiftMax256Right = function(shiftRight){
|
exports.shiftMax256Right = function (shiftRight) {
|
||||||
|
|
||||||
//Max value uint256 (an array of ones representing 256 enabled bits)
|
//Max value uint256 (an array of ones representing 256 enabled bits)
|
||||||
var arr256 = Array.apply(null, new Array(256)).map(Number.prototype.valueOf, 1);
|
var arr256 = Array.apply(null, new Array(256)).map(Number.prototype.valueOf, 1);
|
||||||
|
@ -309,7 +303,7 @@ exports.shiftMax256Right = function(shiftRight){
|
||||||
//An array of bytes to convert the bits to, 8 bits in a byte so length will be 32
|
//An array of bytes to convert the bits to, 8 bits in a byte so length will be 32
|
||||||
var octets = [];
|
var octets = [];
|
||||||
|
|
||||||
for (var i = 0; i < 32; i++){
|
for (var i = 0; i < 32; i++) {
|
||||||
|
|
||||||
octets[i] = 0;
|
octets[i] = 0;
|
||||||
|
|
||||||
|
@ -317,7 +311,7 @@ exports.shiftMax256Right = function(shiftRight){
|
||||||
var bits = arr256.slice(i * 8, i * 8 + 8);
|
var bits = arr256.slice(i * 8, i * 8 + 8);
|
||||||
|
|
||||||
//Bit math to add the bits into a byte
|
//Bit math to add the bits into a byte
|
||||||
for (var f = 0; f < bits.length; f++){
|
for (var f = 0; f < bits.length; f++) {
|
||||||
var multiplier = Math.pow(2, f);
|
var multiplier = Math.pow(2, f);
|
||||||
octets[i] += bits[f] * multiplier;
|
octets[i] += bits[f] * multiplier;
|
||||||
}
|
}
|
||||||
|
@ -328,15 +322,14 @@ exports.shiftMax256Right = function(shiftRight){
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.bufferToCompactBits = function(startingBuff){
|
exports.bufferToCompactBits = function (startingBuff) {
|
||||||
var bigNum = bignum.fromBuffer(startingBuff);
|
var bigNum = bignum.fromBuffer(startingBuff);
|
||||||
var buff = bigNum.toBuffer();
|
var buff = bigNum.toBuffer();
|
||||||
|
|
||||||
buff = buff.readUInt8(0) > 0x7f ? Buffer.concat([new Buffer([0x00]), buff]) : buff;
|
buff = buff.readUInt8(0) > 0x7f ? Buffer.concat([new Buffer([0x00]), buff]) : buff;
|
||||||
|
|
||||||
buff = Buffer.concat([new Buffer([buff.length]), buff]);
|
buff = Buffer.concat([new Buffer([buff.length]), buff]);
|
||||||
var compact = buff.slice(0, 4);
|
return compact = buff.slice(0, 4);
|
||||||
return compact;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -344,25 +337,25 @@ exports.bufferToCompactBits = function(startingBuff){
|
||||||
More info: https://en.bitcoin.it/wiki/Target
|
More info: https://en.bitcoin.it/wiki/Target
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.bignumFromBitsBuffer = function(bitsBuff){
|
exports.bignumFromBitsBuffer = function (bitsBuff) {
|
||||||
var numBytes = bitsBuff.readUInt8(0);
|
var numBytes = bitsBuff.readUInt8(0);
|
||||||
var bigBits = bignum.fromBuffer(bitsBuff.slice(1));
|
var bigBits = bignum.fromBuffer(bitsBuff.slice(1));
|
||||||
var target = bigBits.mul(
|
var target = bigBits.mul(
|
||||||
bignum(2).pow(
|
bignum(2).pow(
|
||||||
bignum(8).mul(
|
bignum(8).mul(
|
||||||
numBytes - 3
|
numBytes - 3
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.bignumFromBitsHex = function(bitsString){
|
exports.bignumFromBitsHex = function (bitsString) {
|
||||||
var bitsBuff = new Buffer(bitsString, 'hex');
|
var bitsBuff = new Buffer(bitsString, 'hex');
|
||||||
return exports.bignumFromBitsBuffer(bitsBuff);
|
return exports.bignumFromBitsBuffer(bitsBuff);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.convertBitsToBuff = function(bitsBuff){
|
exports.convertBitsToBuff = function (bitsBuff) {
|
||||||
var target = exports.bignumFromBitsBuffer(bitsBuff);
|
var target = exports.bignumFromBitsBuffer(bitsBuff);
|
||||||
var resultBuff = target.toBuffer();
|
var resultBuff = target.toBuffer();
|
||||||
var buff256 = new Buffer(32);
|
var buff256 = new Buffer(32);
|
||||||
|
@ -371,6 +364,6 @@ exports.convertBitsToBuff = function(bitsBuff){
|
||||||
return buff256;
|
return buff256;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getTruncatedDiff = function(shift){
|
exports.getTruncatedDiff = function (shift) {
|
||||||
return exports.convertBitsToBuff(exports.bufferToCompactBits(exports.shiftMax256Right(shift)));
|
return exports.convertBitsToBuff(exports.bufferToCompactBits(exports.shiftMax256Right(shift)));
|
||||||
};
|
};
|
|
@ -2,38 +2,40 @@ var events = require('events');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Vardiff ported from stratum-mining share-limiter
|
Vardiff ported from stratum-mining share-limiter
|
||||||
https://github.com/ahmedbodi/stratum-mining/blob/master/mining/basic_share_limiter.py
|
https://github.com/ahmedbodi/stratum-mining/blob/master/mining/basic_share_limiter.py
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
function RingBuffer(maxSize){
|
function RingBuffer(maxSize) {
|
||||||
var data = [];
|
var data = [];
|
||||||
var cursor = 0;
|
var cursor = 0;
|
||||||
var isFull = false;
|
var isFull = false;
|
||||||
this.append = function(x){
|
this.append = function (x) {
|
||||||
if (isFull){
|
if (isFull) {
|
||||||
data[cursor] = x;
|
data[cursor] = x;
|
||||||
cursor = (cursor + 1) % maxSize;
|
cursor = (cursor + 1) % maxSize;
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
data.push(x);
|
data.push(x);
|
||||||
cursor++;
|
cursor++;
|
||||||
if (data.length === maxSize){
|
if (data.length === maxSize) {
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
isFull = true;
|
isFull = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.avg = function(){
|
this.avg = function () {
|
||||||
var sum = data.reduce(function(a, b){ return a + b });
|
var sum = data.reduce(function (a, b) {
|
||||||
|
return a + b
|
||||||
|
});
|
||||||
return sum / (isFull ? maxSize : cursor);
|
return sum / (isFull ? maxSize : cursor);
|
||||||
};
|
};
|
||||||
this.size = function(){
|
this.size = function () {
|
||||||
return isFull ? maxSize : cursor;
|
return isFull ? maxSize : cursor;
|
||||||
};
|
};
|
||||||
this.clear = function(){
|
this.clear = function () {
|
||||||
data = [];
|
data = [];
|
||||||
cursor = 0;
|
cursor = 0;
|
||||||
isFull = false;
|
isFull = false;
|
||||||
|
@ -45,7 +47,7 @@ function toFixed(num, len) {
|
||||||
return parseFloat(num.toFixed(len));
|
return parseFloat(num.toFixed(len));
|
||||||
}
|
}
|
||||||
|
|
||||||
var varDiff = module.exports = function varDiff(port, varDiffOptions){
|
var varDiff = module.exports = function varDiff(port, varDiffOptions) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
|
||||||
var bufferSize, tMin, tMax;
|
var bufferSize, tMin, tMax;
|
||||||
|
@ -56,12 +58,11 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
|
||||||
|
|
||||||
|
|
||||||
bufferSize = varDiffOptions.retargetTime / varDiffOptions.targetTime * 4;
|
bufferSize = varDiffOptions.retargetTime / varDiffOptions.targetTime * 4;
|
||||||
tMin = varDiffOptions.targetTime - variance;
|
tMin = varDiffOptions.targetTime - variance;
|
||||||
tMax = varDiffOptions.targetTime + variance;
|
tMax = varDiffOptions.targetTime + variance;
|
||||||
|
|
||||||
|
|
||||||
|
this.manageClient = function (client) {
|
||||||
this.manageClient = function(client){
|
|
||||||
|
|
||||||
var stratumPort = client.socket.localPort;
|
var stratumPort = client.socket.localPort;
|
||||||
|
|
||||||
|
@ -74,11 +75,11 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
|
||||||
var lastRtc;
|
var lastRtc;
|
||||||
var timeBuffer;
|
var timeBuffer;
|
||||||
|
|
||||||
client.on('submit', function(){
|
client.on('submit', function () {
|
||||||
|
|
||||||
var ts = (Date.now() / 1000) | 0;
|
var ts = (Date.now() / 1000) | 0;
|
||||||
|
|
||||||
if (!lastRtc){
|
if (!lastRtc) {
|
||||||
lastRtc = ts - options.retargetTime / 2;
|
lastRtc = ts - options.retargetTime / 2;
|
||||||
lastTs = ts;
|
lastTs = ts;
|
||||||
timeBuffer = new RingBuffer(bufferSize);
|
timeBuffer = new RingBuffer(bufferSize);
|
||||||
|
@ -113,7 +114,7 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
|
||||||
ddiff = diffMax / client.difficulty;
|
ddiff = diffMax / client.difficulty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
13
package.json
13
package.json
|
@ -12,9 +12,9 @@
|
||||||
"litecoin",
|
"litecoin",
|
||||||
"scrypt"
|
"scrypt"
|
||||||
],
|
],
|
||||||
"homepage": "https://github.com/zone117x/node-stratum-pool",
|
"homepage": "https://github.com/joshuayabut/node-stratum-pool",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/zone117x/node-stratum-pool/issues"
|
"url": "https://github.com/joshuayabut/node-stratum-pool/issues"
|
||||||
},
|
},
|
||||||
"license": "GPL-2.0",
|
"license": "GPL-2.0",
|
||||||
"author": "Matthew Little",
|
"author": "Matthew Little",
|
||||||
|
@ -25,13 +25,16 @@
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/zone117x/node-stratum-pool.git"
|
"url": "https://github.com/joshuayabut/node-stratum-pool.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"multi-hashing": "git://github.com/zone117x/node-multi-hashing.git",
|
"multi-hashing": "https://github.com/joshuayabut/node-multi-hashing.git",
|
||||||
"bignum": "*",
|
"bignum": "*",
|
||||||
"base58-native": "*",
|
"base58-native": "*",
|
||||||
"async": "*"
|
"async": "*",
|
||||||
|
"merkle-bitcoin": "https://github.com/joshuayabut/merkle-bitcoin.git",
|
||||||
|
"bitcoinjs-lib": "*",
|
||||||
|
"promise": "*"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
|
|
Loading…
Reference in New Issue