Initial Z-NOMP Commit

This commit is contained in:
joshuayabut 2016-11-16 17:27:48 -05:00
parent 02e78da1ab
commit 4cb9d21ccb
13 changed files with 634 additions and 1028 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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