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 util = require('./util.js');
var diff1 = global.diff1 = 0x00000000ffff0000000000000000000000000000000000000000000000000000;
var diff1 = global.diff1 = 0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
var algos = module.exports = global.algos = {
sha256: {
@ -14,193 +14,18 @@ var algos = module.exports = global.algos = {
}
}
},
'scrypt': {
//Uncomment diff if you want to use hardcoded truncated diff
//diff: '0000ffff00000000000000000000000000000000000000000000000000000000',
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: {
'equihash': {
multiplier: 1,
diff: parseInt('0x0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
hash: function(){
return function(){
return multiHashing.sha1.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);
return multiHashing.equihash.apply(this, arguments);
}
}
}
};
for (var algo in algos){
if (!algos[algo].multiplier)
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,139 +1,140 @@
var bignum = require('bignum');
var merkleTree = require('./merkleTree.js');
var merkle = require('./merkleTree.js');
var transactions = require('./transactions.js');
var util = require('./util.js');
/**
* The BlockTemplate class holds a single job.
* 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
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
this.rpcData = rpcData;
this.jobId = jobId;
this.target = rpcData.target ?
bignum(rpcData.target, 16) :
util.bignumFromBitsHex(rpcData.bits);
// get target info
this.target = bignum(rpcData.target, 16);
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');
this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){
return new Buffer(tx.data, 'hex');
}));
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);
//block header per https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
this.serializeHeader = function(nTime, nonce){
var header = new Buffer(140);
var position = 0;
header.write(nonce, position, 4, 'hex');
header.write(rpcData.bits, position += 4, 4, 'hex');
header.write(nTime, position += 4, 4, 'hex');
header.write(merkleRoot, position += 4, 32, 'hex');
header.write(rpcData.previousblockhash, position += 32, 32, 'hex');
header.writeUInt32BE(rpcData.version, position + 32);
var header = util.reverseBuffer(header);
/*
console.log('nonce:' + nonce);
console.log('this.rpcData.bits: ' + this.rpcData.bits);
console.log('nTime: ' + nTime);
console.log('this.merkleRootReversed: ' + this.merkleRoot);
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;
};
this.serializeBlock = function(header, coinbase){
return Buffer.concat([
// join the header and txs together
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,
util.varIntBuffer(this.rpcData.transactions.length + 1),
coinbase,
this.transactionData,
getVoteData(),
//POS coins require a zero byte appended to block which the daemon replaces with the signature
new Buffer(reward === 'POS' ? [0] : [])
soln,
varInt,
Buffer(this.genTx, 'hex')
]);
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){
var submission = extraNonce1 + extraNonce2 + nTime + nonce;
// submit the block header
this.registerSubmit = function(header, soln){
var submission = header + soln;
if (submits.indexOf(submission) === -1){
submits.push(submission);
return true;
}
return false;
};
// used for mining.notify
this.getJobParams = function(){
if (!this.jobParams){
this.jobParams = [
this.jobId,
util.packUInt32LE(this.rpcData.version).toString('hex'),
this.prevHashReversed,
this.generationTransaction[0].toString('hex'),
this.generationTransaction[1].toString('hex'),
this.merkleBranch,
util.packInt32BE(this.rpcData.version).toString('hex'),
this.rpcData.bits,
util.packUInt32BE(this.rpcData.curtime).toString('hex'),
this.merkleRootReversed,
'0000000000000000000000000000000000000000000000000000000000000000', //hashReserved
util.packUInt32LE(rpcData.curtime).toString('hex'),
util.reverseBuffer(Buffer(this.rpcData.bits, 'hex')).toString('hex'),
true
];
}
return this.jobParams;
};
};
};

View File

@ -4,41 +4,41 @@ var events = require('events');
var async = require('async');
/**
/**
* The daemon interface interacts with the coin daemon by using the rpc interface.
* in order to make it work it needs, as constructor, an array of objects containing
* - 'host' : hostname where the coin lives
* - 'port' : port where the coin accepts rpc connections
* - 'user' : username of the coin for the rpc interface
* - 'password': password for the rpc interface of the coin
**/
**/
function DaemonInterface(daemons, logger){
function DaemonInterface(daemons, logger) {
//private members
var _this = this;
logger = logger || function(severity, message){
console.log(severity + ': ' + message);
};
logger = logger || function (severity, message) {
console.log(severity + ': ' + message);
};
var instances = (function(){
var instances = (function () {
for (var i = 0; i < daemons.length; i++)
daemons[i]['index'] = i;
return daemons;
})();
function init(){
isOnline(function(online){
function init() {
isOnline(function (online) {
if (online)
_this.emit('online');
});
}
function isOnline(callback){
cmd('getinfo', [], function(results){
var allOnline = results.every(function(result){
function isOnline(callback) {
cmd('getinfo', [], function (results) {
var allOnline = results.every(function (result) {
return !results.error;
});
callback(allOnline);
@ -48,30 +48,30 @@ function DaemonInterface(daemons, logger){
}
function performHttpRequest(instance, jsonData, callback){
function performHttpRequest(instance, jsonData, callback) {
var options = {
hostname: (typeof(instance.host) === 'undefined' ? '127.0.0.1' : instance.host),
port : instance.port,
method : 'POST',
auth : instance.user + ':' + instance.password,
headers : {
port: instance.port,
method: 'POST',
auth: instance.user + ':' + instance.password,
headers: {
'Content-Length': jsonData.length
}
};
var parseJson = function(res, data){
var parseJson = function (res, data) {
var dataJson;
if (res.statusCode === 401){
if (res.statusCode === 401) {
logger('error', 'Unauthorized RPC access - invalid RPC username or password');
return;
}
try{
try {
dataJson = JSON.parse(data);
}
catch(e){
if (data.indexOf(':-nan') !== -1){
catch (e) {
if (data.indexOf(':-nan') !== -1) {
data = data.replace(/:-nan,/g, ":0");
parseJson(res, data);
return;
@ -85,18 +85,18 @@ function DaemonInterface(daemons, logger){
callback(dataJson.error, dataJson, data);
};
var req = http.request(options, function(res) {
var req = http.request(options, function (res) {
var data = '';
res.setEncoding('utf8');
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function(){
res.on('end', function () {
parseJson(res, data);
});
});
req.on('error', function(e) {
req.on('error', function (e) {
if (e.code === 'ECONNREFUSED')
callback({type: 'offline', message: e.message}, null);
else
@ -107,20 +107,19 @@ function DaemonInterface(daemons, logger){
}
//Performs a batch JSON-RPC command - only uses the first configured rpc daemon
/* First argument must have:
[
[ methodName, [params] ],
[ methodName, [params] ]
[ methodName, [params] ],
[ methodName, [params] ]
]
*/
function batchCmd(cmdArray, callback){
function batchCmd(cmdArray, callback) {
var requestJson = [];
for (var i = 0; i < cmdArray.length; i++){
for (var i = 0; i < cmdArray.length; i++) {
requestJson.push({
method: cmdArray[i][0],
params: cmdArray[i][1],
@ -130,22 +129,22 @@ function DaemonInterface(daemons, logger){
var serializedRequest = JSON.stringify(requestJson);
performHttpRequest(instances[0], serializedRequest, function(error, result){
performHttpRequest(instances[0], serializedRequest, function (error, result) {
callback(error, result);
});
}
/* 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
set to true. */
function cmd(method, params, callback, streamResults, returnRawData){
The callback function is fired once with the result from each daemon unless streamResults is
set to true. */
function cmd(method, params, callback, streamResults, returnRawData) {
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 = {
error: error,
@ -156,7 +155,8 @@ function DaemonInterface(daemons, logger){
if (streamResults) callback(returnObj);
else results.push(returnObj);
eachCallback();
itemFinished = function(){};
itemFinished = function () {
};
};
var requestJson = JSON.stringify({
@ -165,13 +165,13 @@ function DaemonInterface(daemons, logger){
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);
});
}, function(){
if (!streamResults){
}, function () {
if (!streamResults) {
callback(results);
}
});

View File

@ -10,7 +10,7 @@ exports.daemon = require('./daemon.js');
exports.varDiff = require('./varDiff.js');
exports.createPool = function(poolOptions, authorizeFn){
exports.createPool = function (poolOptions, authorizeFn) {
var newPool = new pool(poolOptions, authorizeFn);
return newPool;
};

View File

@ -4,33 +4,28 @@ var crypto = require('crypto');
var bignum = require('bignum');
var util = require('./util.js');
var blockTemplate = require('./blockTemplate.js');
//Unique extranonce per subscriber
var ExtraNonceCounter = function(configInstanceId){
var ExtraNonceCounter = function (configInstanceId) {
var instanceId = configInstanceId || crypto.randomBytes(4).readUInt32LE(0);
var counter = instanceId << 27;
this.next = function(){
this.next = function () {
var extraNonce = util.packUInt32BE(Math.abs(counter++));
return extraNonce.toString('hex');
};
this.size = 4; //bytes
};
//Unique job per new block template
var JobCounter = function(){
var counter = 0;
var JobCounter = function () {
var counter = 0x0000cccc;
this.next = function(){
this.next = function () {
counter++;
if (counter % 0xffff === 0)
if (counter % 0xffffffffff === 0)
counter = 1;
return this.cur();
};
@ -44,8 +39,8 @@ var JobCounter = function(){
* Emits:
* - 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
**/
var JobManager = module.exports = function JobManager(options){
**/
var JobManager = module.exports = function JobManager(options) {
//private members
@ -54,28 +49,18 @@ var JobManager = module.exports = function JobManager(options){
var jobCounter = new JobCounter();
var shareMultiplier = algos[options.coin.algorithm].multiplier;
//public members
this.extraNonceCounter = new ExtraNonceCounter(options.instanceId);
this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex');
this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size;
this.currentJob;
this.validJobs = {};
var hashDigest = algos[options.coin.algorithm].hash(options.coin);
var coinbaseHasher = (function(){
switch(options.coin.algorithm){
case 'keccak':
case 'blake':
case 'fugue':
case 'groestl':
if (options.coin.normalHashing === true)
return util.sha256d;
else
return util.sha256;
var coinbaseHasher = (function () {
switch (options.coin.algorithm) {
default:
return util.sha256d;
}
@ -84,46 +69,25 @@ var JobManager = module.exports = function JobManager(options){
var blockHasher = (function () {
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':
return function (d) {
return util.reverseBuffer(util.sha256d(d));
};
default:
return function () {
return util.reverseBuffer(hashDigest.apply(this, arguments));
return function (d) {
return util.reverseBuffer(util.sha256(d));
};
}
})();
this.updateCurrentJob = function(rpcData){
this.updateCurrentJob = function (rpcData) {
var tmpBlockTemplate = new blockTemplate(
jobCounter.next(),
rpcData,
options.poolAddressScript,
_this.extraNoncePlaceholder,
options.coin.reward,
options.coin.txMessages,
options.recipients
options.recipients,
options.address
);
_this.currentJob = tmpBlockTemplate;
@ -135,12 +99,12 @@ var JobManager = module.exports = function JobManager(options){
};
//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 height is greater than the one we have */
block height is greater than the one we have */
var isNewBlock = typeof(_this.currentJob) === 'undefined';
if (!isNewBlock && _this.currentJob.rpcData.previousblockhash !== rpcData.previousblockhash){
if (!isNewBlock && _this.currentJob.rpcData.previousblockhash !== rpcData.previousblockhash) {
isNewBlock = true;
//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(
jobCounter.next(),
rpcData,
options.poolAddressScript,
_this.extraNoncePlaceholder,
options.coin.reward,
options.coin.txMessages,
options.recipients
options.recipients,
options.address
);
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){
var shareError = function(error){
this.processShare = function (jobId, previousDifficulty, difficulty, extraNonce1, extraNonce2, nTime, nonce, ipAddress, port, workerName, soln) {
var shareError = function (error) {
_this.emit('share', {
job: jobId,
ip: ipAddress,
@ -186,12 +149,9 @@ var JobManager = module.exports = function JobManager(options){
var submitTime = Date.now() / 1000 | 0;
if (extraNonce2.length / 2 !== _this.extraNonce2Size)
return shareError([20, 'incorrect size of extranonce2']);
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']);
}
@ -199,30 +159,29 @@ var JobManager = module.exports = function JobManager(options){
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) {
return shareError([20, 'ntime out of range']);
}
if (nonce.length !== 8) {
if (nonce.length !== 64) {
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)) {
return shareError([22, 'duplicate share']);
}
var extraNonce1Buffer = new Buffer(extraNonce1, 'hex');
var extraNonce2Buffer = new Buffer(extraNonce2, 'hex');
var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer);
var coinbaseHash = coinbaseHasher(coinbaseBuffer);
var merkleRoot = util.reverseBuffer(job.merkleTree.withFirst(coinbaseHash)).toString('hex');
var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce);
var headerHash = hashDigest(headerBuffer, nTimeInt);
var headerBuffer = job.serializeHeader(nTime, nonce); // 144 bytes (doesn't contain soln)
var headerSolnBuffer = Buffer.concat([headerBuffer, Buffer(soln, 'hex')]);
var headerHash = util.sha256d(headerSolnBuffer);
var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32});
var blockHashInvalid;
@ -230,32 +189,47 @@ var JobManager = module.exports = function JobManager(options){
var blockHex;
var shareDiff = diff1 / headerBigNum.toNumber() * shareMultiplier;
var blockDiffAdjusted = job.difficulty * shareMultiplier;
//Check if share is a block candidate (matched network difficulty)
if (job.target.ge(headerBigNum)){
blockHex = job.serializeBlock(headerBuffer, coinbaseBuffer).toString('hex');
blockHash = blockHasher(headerBuffer, nTime).toString('hex');
//check if block candidate and if the equihash solution is valid
//if (1 === 1) {
if (headerBigNum.le(job.target) && hashDigest(headerBuffer, Buffer(soln.slice(6), 'hex'))) {
blockHex = job.serializeBlock(headerBuffer, Buffer(soln, 'hex')).toString('hex');
blockHash = blockHasher(headerBuffer, Buffer(soln, 'hex')).toString('hex');
}
else {
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)
if (shareDiff / difficulty < 0.99){
if (shareDiff / difficulty < 0.99) {
//Check if share matched a previous difficulty from before a vardiff retarget
if (previousDifficulty && shareDiff >= previousDifficulty){
if (previousDifficulty && shareDiff >= previousDifficulty) {
difficulty = previousDifficulty;
}
else{
else {
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', {
job: jobId,
@ -264,9 +238,9 @@ var JobManager = module.exports = function JobManager(options){
worker: workerName,
height: job.rpcData.height,
blockReward: job.rpcData.coinbasevalue,
difficulty: difficulty,
difficulty: job.difficulty, // FIXME
shareDiff: shareDiff.toFixed(8),
blockDiff : blockDiffAdjusted,
blockDiff: blockDiffAdjusted,
blockDiffActual: job.difficulty,
blockHash: blockHash,
blockHashInvalid: blockHashInvalid

View File

@ -1,58 +1,22 @@
/*
Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py
*/
var Promise = require('promise');
var merklebitcoin = Promise.denodeify(require('merkle-bitcoin'));
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){
this.steps.forEach(function(s){
f = util.sha256d(Buffer.concat([f, s]));
});
return f;
exports.getRoot = function (rpcData, generateTxRaw) {
hashes = [generateTxRaw];
rpcData.transactions.forEach(function (value) {
hashes.push(value.hash);
});
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
var fixedLenStringBuffer = function(s, len) {
var fixedLenStringBuffer = function (s, len) {
var buff = new Buffer(len);
buff.fill(0);
buff.write(s);
@ -20,10 +20,10 @@ var commandStringBuffer = function (s) {
};
/* Reads a set amount of bytes from a flowing stream, argument descriptions:
- stream to read from, must have data emitter
- amount of bytes to read
- preRead argument can be used to set start with an existing data buffer
- callback returns 1) data buffer and 2) lopped/over-read data */
- stream to read from, must have data emitter
- amount of bytes to read
- preRead argument can be used to set start with an existing data buffer
- callback returns 1) data buffer and 2) lopped/over-read data */
var readFlowingBytes = function (stream, amount, preRead, callback) {
var buff = preRead ? preRead : new Buffer([]);
@ -57,7 +57,7 @@ var Peer = module.exports = function (options) {
tx: 1,
block: 2
};
var networkServices = new Buffer('0100000000000000', 'hex'); //NODE_NETWORK services (value 1 packed as uint64)
var emptyNetAddress = new Buffer('010000000000000000000000000000000000ffff000000000000', 'hex');
var userAgent = util.varStringBuffer('/node-stratum/');
@ -155,13 +155,12 @@ var Peer = module.exports = function (options) {
//sloppy varint decoding
var count = payload.readUInt8(0);
payload = payload.slice(1);
if (count >= 0xfd)
{
if (count >= 0xfd) {
count = payload.readUInt16LE(0);
payload = payload.slice(2);
}
while (count--) {
switch(payload.readUInt32LE(0)) {
switch (payload.readUInt32LE(0)) {
case invCodes.error:
break;
case invCodes.tx:
@ -183,7 +182,7 @@ var Peer = module.exports = function (options) {
HandleInv(payload);
break;
case commands.verack.toString():
if(!verack) {
if (!verack) {
verack = true;
_this.emit('connected');
}

View File

@ -9,11 +9,11 @@ var jobManager = require('./jobManager.js');
var util = require('./util.js');
/*process.on('uncaughtException', function(err) {
console.log(err.stack);
throw err;
});*/
console.log(err.stack);
throw err;
});*/
var pool = module.exports = function pool(options, authorizeFn){
var pool = module.exports = function pool(options, authorizeFn) {
this.options = options;
@ -21,32 +21,38 @@ var pool = module.exports = function pool(options, authorizeFn){
var blockPollingIntervalId;
var emitLog = function(text) { _this.emit('log', 'debug' , 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); };
var emitLog = function (text) {
_this.emit('log', 'debug', 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.');
throw new Error();
}
this.start = function(){
this.start = function () {
SetupVarDiff();
SetupApi();
SetupDaemonInterface(function(){
DetectCoinData(function(){
SetupDaemonInterface(function () {
DetectCoinData(function () {
SetupRecipients();
SetupJobManager();
OnBlockchainSynced(function(){
GetFirstJob(function(){
OnBlockchainSynced(function () {
GetFirstJob(function () {
SetupBlockPolling();
SetupPeer();
StartStratumServer(function(){
StartStratumServer(function () {
OutputPoolInfo();
_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) {
emitErrorLog('Error with getblocktemplate on creating first job, server cannot start');
return;
@ -70,7 +75,7 @@ var pool = module.exports = function pool(options, authorizeFn){
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;
if (networkDiffAdjusted < 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 +
' [' + 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);
return;
}
var infoLines = [startMessage,
'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'),
'Detected Reward Type:\t' + options.coin.reward,
'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,
'Network Difficulty:\t' + options.initStats.difficulty,
'Network Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate),
'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '),
'Pool Fee Percent:\t' + _this.options.feePercent + '%'
'Network Connected:\t' + (options.testnet ? 'Testnet' : 'Mainnet'),
'Detected Reward Type:\t' + options.coin.reward,
'Current Block Height:\t' + _this.jobManager.currentJob.rpcData.height,
'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 Hash Rate:\t' + util.getReadableHashRateString(options.initStats.networkHashRate),
'Stratum Port(s):\t' + _this.options.initStats.stratumPorts.join(', '),
'Pool Fee Percent:\t' + _this.options.feePercent + '%'
];
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){
_this.daemon.cmd('getblocktemplate', [], function(results){
var synced = results.every(function(r){
var checkSynced = function (displayNotSynced) {
_this.daemon.cmd('getblocktemplate', [], function (results) {
var synced = results.every(function (r) {
return !r.error || r.error.code !== -10;
});
if (synced){
if (synced) {
syncedCallback();
}
else{
else {
if (displayNotSynced) displayNotSynced();
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
if (!process.env.forkId || process.env.forkId === '0')
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) {
return b.response.blocks - a.response.blocks;
})[0].response.blocks;
//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 totalBlocks = peers.sort(function(a, b){
var totalBlocks = peers.sort(function (a, b) {
return b.startingheight - a.startingheight;
})[0].startingheight;
@ -171,48 +176,47 @@ var pool = module.exports = function pool(options, authorizeFn){
function SetupApi() {
if (typeof(options.api) !== 'object' || typeof(options.api.start) !== 'function') {
return;
} else {
options.api.start(_this);
}
}
function SetupPeer(){
function SetupPeer() {
if (!options.p2p || !options.p2p.enabled)
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');
return;
}
else if (!options.coin.peerMagic){
else if (!options.coin.peerMagic) {
emitErrorLog('p2p cannot be enabled without peerMagic set in coin configuration');
return;
}
_this.peer = new peer(options);
_this.peer.on('connected', function() {
_this.peer.on('connected', function () {
emitLog('p2p connection successful');
}).on('connectionRejected', function(){
}).on('connectionRejected', function () {
emitErrorLog('p2p connection failed - likely incorrect p2p magic value');
}).on('disconnected', function(){
}).on('disconnected', function () {
emitWarningLog('p2p peer node disconnected - attempting reconnection...');
}).on('connectionFailed', function(e){
}).on('connectionFailed', function (e) {
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));
}).on('error', function(msg){
}).on('error', function (msg) {
emitWarningLog('p2p had an error ' + msg);
}).on('blockFound', function(hash){
}).on('blockFound', function (hash) {
_this.processBlockNotify(hash, 'p2p');
});
}
function SetupVarDiff(){
function SetupVarDiff() {
_this.varDiff = {};
Object.keys(options.ports).forEach(function(port) {
Object.keys(options.ports).forEach(function (port) {
if (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;
if (options.hasSubmitMethod){
if (options.hasSubmitMethod) {
rpcCommand = 'submitblock';
rpcArgs = [blockHex];
}
else{
else {
rpcCommand = 'getblocktemplate';
rpcArgs = [{'mode': 'submit', 'data': blockHex}];
}
@ -237,13 +241,13 @@ var pool = module.exports = function pool(options, authorizeFn){
_this.daemon.cmd(rpcCommand,
rpcArgs,
function(results){
for (var i = 0; i < results.length; i++){
function (results) {
for (var i = 0; i < results.length; i++) {
var result = results[i];
if (result.error) {
emitErrorLog('rpc error with daemon instance ' +
result.instance.index + ' when submitting block with ' + rpcCommand + ' ' +
JSON.stringify(result.error)
result.instance.index + ' when submitting block with ' + rpcCommand + ' ' +
JSON.stringify(result.error)
);
return;
}
@ -256,15 +260,16 @@ var pool = module.exports = function pool(options, authorizeFn){
callback();
}
);
}
function SetupRecipients(){
function SetupRecipients() {
var recipients = [];
options.feePercent = 0;
options.rewardRecipients = options.rewardRecipients || {};
for (var r in options.rewardRecipients){
//options.rewardRecipients = options.rewardRecipients || {};
options.rewardRecipients = options.recipients;
/*
for (var r in options.rewardRecipients) {
var percent = options.rewardRecipients[r];
var rObj = {
percent: percent / 100
@ -277,54 +282,55 @@ var pool = module.exports = function pool(options, authorizeFn){
recipients.push(rObj);
options.feePercent += percent;
}
catch(e){
catch (e) {
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');
}
options.recipients = recipients;
}
function SetupJobManager(){
function SetupJobManager() {
_this.jobManager = new jobManager(options);
_this.jobManager.on('newBlock', function(blockTemplate){
_this.jobManager.on('newBlock', function (blockTemplate) {
//Check if stratumServer has been initialized yet
if (_this.stratumServer) {
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
}
}).on('updatedBlock', function(blockTemplate){
}).on('updatedBlock', function (blockTemplate) {
//Check if stratumServer has been initialized yet
if (_this.stratumServer) {
var job = blockTemplate.getJobParams();
job[8] = false;
_this.stratumServer.broadcastMiningJobs(job);
}
}).on('share', function(shareData, blockHex){
}).on('share', function (shareData, blockHex) {
var isValidShare = !shareData.error;
var isValidBlock = !!blockHex;
var emitShare = function(){
var emitShare = function () {
_this.emit('share', isValidShare, isValidBlock, shareData);
};
/*
If we calculated that the block solution was found,
before we emit the share, lets submit the block,
then check if it was accepted using RPC getblock
*/
If we calculated that the block solution was found,
before we emit the share, lets submit the block,
then check if it was accepted using RPC getblock
*/
if (!isValidBlock)
emitShare();
else{
SubmitBlock(blockHex, function(){
CheckBlockAccepted(shareData.blockHash, function(isAccepted, tx){
else {
SubmitBlock(blockHex, function () {
CheckBlockAccepted(shareData.blockHash, function (isAccepted, tx) {
isValidBlock = isAccepted;
shareData.txHash = tx;
emitShare();
GetBlockTemplate(function(error, result, foundNewBlock){
GetBlockTemplate(function (error, result, foundNewBlock) {
if (foundNewBlock)
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);
});
}
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');
return;
}
_this.daemon = new daemon.interface(options.daemons, function(severity, message){
_this.emit('log', severity , message);
_this.daemon = new daemon.interface(options.daemons, function (severity, message) {
_this.emit('log', severity, message);
});
_this.daemon.once('online', function(){
_this.daemon.once('online', function () {
finishedCallback();
}).on('connectionFailed', function(error){
}).on('connectionFailed', function (error) {
emitErrorLog('Failed to connect daemon(s): ' + JSON.stringify(error));
}).on('error', function(message){
}).on('error', function (message) {
emitErrorLog(message);
});
@ -364,7 +370,7 @@ var pool = module.exports = function pool(options, authorizeFn){
}
function DetectCoinData(finishedCallback){
function DetectCoinData(finishedCallback) {
var batchRpcCalls = [
['validateaddress', [options.address]],
@ -374,26 +380,26 @@ var pool = module.exports = function pool(options, authorizeFn){
['submitblock', []]
];
_this.daemon.batchCmd(batchRpcCalls, function(error, results){
if (error || !results){
_this.daemon.batchCmd(batchRpcCalls, function (error, results) {
if (error || !results) {
emitErrorLog('Could not start pool, error with init batch RPC call: ' + JSON.stringify(error));
return;
}
var rpcResults = {};
for (var i = 0; i < results.length; i++){
for (var i = 0; i < results.length; i++) {
var rpcCall = batchRpcCalls[i][0];
var r = results[i];
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));
return;
}
}
if (!rpcResults.validateaddress.isvalid){
if (!rpcResults.validateaddress.isvalid) {
emitErrorLog('Daemon reports address is not valid');
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
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') {
emitErrorLog('The address provided is not from the daemon wallet - this is required for POS coins.');
return;
}
options.poolAddressScript = (function(){
switch(options.coin.reward){
case 'POS':
return util.pubkeyToScript(rpcResults.validateaddress.pubkey);
case 'POW':
return util.addressToScript(rpcResults.validateaddress.address);
}
options.poolAddressScript = (function () {
return util.addressToScript(rpcResults.validateaddress.address);
})();
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;
}
else if (rpcResults.submitblock.code === -1){
else if (rpcResults.submitblock.code === -1) {
options.hasSubmitMethod = true;
}
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.on('started', function(){
_this.stratumServer.on('started', function () {
options.initStats.stratumPorts = Object.keys(options.ports);
_this.stratumServer.broadcastMiningJobs(_this.jobManager.currentJob.getJobParams());
finishedCallback();
}).on('broadcastTimeout', function(){
}).on('broadcastTimeout', function () {
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;
_this.jobManager.updateCurrentJob(rpcData);
});
}).on('client.connected', function(client){
}).on('client.connected', function (client) {
if (typeof(_this.varDiff[client.socket.localPort]) !== 'undefined') {
_this.varDiff[client.socket.localPort].manageClient(client);
}
client.on('difficultyChanged', function(diff){
client.on('difficultyChanged', function (diff) {
_this.emit('difficultyUpdate', client.workerName, diff);
}).on('subscription', function(params, resultCallback){
}).on('subscription', function (params, resultCallback) {
var extraNonce = _this.jobManager.extraNonceCounter.next();
var extraNonce2Size = _this.jobManager.extraNonce2Size;
resultCallback(null,
extraNonce,
extraNonce2Size
extraNonce
);
// FIXME
if (typeof(options.ports[client.socket.localPort]) !== 'undefined' && options.ports[client.socket.localPort].diff) {
this.sendDifficulty(options.ports[client.socket.localPort].diff);
} else {
this.sendDifficulty(8);
}
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
}).on('submit', function(params, resultCallback){
var result =_this.jobManager.processShare(
}).on('submit', function (params, resultCallback) {
var result = _this.jobManager.processShare(
params.jobId,
client.previousDifficulty,
client.difficulty,
@ -502,7 +503,8 @@ var pool = module.exports = function pool(options, authorizeFn){
params.nonce,
client.remoteAddress,
client.socket.localPort,
params.name
params.name,
params.soln
);
resultCallback(result.error, result.result ? true : null);
@ -510,34 +512,34 @@ var pool = module.exports = function pool(options, authorizeFn){
}).on('malformedMessage', function (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));
}).on('socketTimeout', function(reason){
}).on('socketTimeout', function (reason) {
emitWarningLog('Connected timed out for ' + client.getLabel() + ': ' + reason)
}).on('socketDisconnect', function() {
}).on('socketDisconnect', function () {
//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');
}).on('forgaveBannedIP', function(){
}).on('forgaveBannedIP', function () {
emitLog('Forgave banned IP ' + client.remoteAddress);
}).on('unknownStratumMethod', function(fullMessage) {
}).on('unknownStratumMethod', function (fullMessage) {
emitLog('Unknown stratum method from ' + client.getLabel() + ': ' + fullMessage.method);
}).on('socketFlooded', function() {
}).on('socketFlooded', function () {
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);
}).on('bootedBannedWorker', function(){
}).on('bootedBannedWorker', function () {
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);
_this.emit('banIP', client.remoteAddress, client.workerName);
});
@ -545,9 +547,8 @@ var pool = module.exports = function pool(options, authorizeFn){
}
function SetupBlockPolling(){
if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0){
function SetupBlockPolling() {
if (typeof options.blockRefreshInterval !== "number" || options.blockRefreshInterval <= 0) {
emitLog('Block template polling has been disabled');
return;
}
@ -555,7 +556,7 @@ var pool = module.exports = function pool(options, authorizeFn){
var pollingInterval = options.blockRefreshInterval;
blockPollingIntervalId = setInterval(function () {
GetBlockTemplate(function(error, result, foundNewBlock){
GetBlockTemplate(function (error, result, foundNewBlock) {
if (foundNewBlock)
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',
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
function(result){
if (result.error){
[{"capabilities": ["coinbasetxn", "workid", "coinbase/append"]}],
function (result) {
if (result.error) {
emitErrorLog('getblocktemplate call failed for daemon instance ' +
result.instance.index + ' with error ' + JSON.stringify(result.error));
callback(result.error);
} else {
var processedNewBlock = _this.jobManager.processTemplate(result.response);
callback(null, result.response, processedNewBlock);
callback = function(){};
callback = function () {
};
}
}, true
);
}
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);
}
function GetBlockSubsidy(callback) {
_this.daemon.cmd('getblocksubsidy',
[],
function (result) {
if (result.error) {
emitErrorLog('getblocksubsidy call failed for daemon instance ' +
result.instance.index + ' with error ' + JSON.stringify(result.error));
callback(result.error);
} else {
var processedNewBlock = _this.jobManager.processTemplate(result.response);
callback(result.response);
callback = function () {
};
}
);
//}, 500);
}, true
);
}
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
* 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);
if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
GetBlockTemplate(function(error, result){
if (typeof(_this.jobManager.currentJob) !== 'undefined' && blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash) {
GetBlockTemplate(function (error, result) {
if (error)
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 stratumClients = [];
@ -631,11 +649,11 @@ var pool = module.exports = function pool(options, authorizeFn){
stratumClients,
filterFn,
function (clientsToRelinquish) {
clientsToRelinquish.forEach(function(cObj) {
clientsToRelinquish.forEach(function (cObj) {
cObj.client.removeAllListeners();
_this.stratumServer.removeStratumClientBySubId(cObj.subId);
});
process.nextTick(function () {
resultCback(
clientsToRelinquish.map(
@ -643,14 +661,14 @@ var pool = module.exports = function pool(options, authorizeFn){
return item.client;
}
)
);
);
});
}
)
};
this.attachMiners = function(miners) {
this.attachMiners = function (miners) {
miners.forEach(function (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;
};
this.setVarDiff = function(port, varDiffConfig) {
if (typeof(_this.varDiff[port]) != 'undefined' ) {
this.setVarDiff = function (port, varDiffConfig) {
if (typeof(_this.varDiff[port]) != 'undefined') {
_this.varDiff[port].removeAllListeners();
}
var varDiffInstance = new varDiff(port, varDiffConfig);
_this.varDiff[port] = varDiffInstance;
_this.varDiff[port].on('newDifficulty', function(client, newDiff) {
_this.varDiff[port] = new varDiff(port, varDiffConfig);
_this.varDiff[port].on('newDifficulty', function (client, newDiff) {
/* We request to set the newDiff @ the next difficulty retarget
(which should happen when a new job comes in - AKA BLOCK) */
client.enqueueNextDifficulty(newDiff);
/*if (options.varDiff.mode === 'fast'){
//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
//so the miner doesn't restart work and submit duplicate shares
client.sendDifficulty(newDiff);
var job = _this.jobManager.currentJob.getJobParams();
job[8] = false;
client.sendMiningJob(job);
}*/
//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
//so the miner doesn't restart work and submit duplicate shares
client.sendDifficulty(newDiff);
var job = _this.jobManager.currentJob.getJobParams();
job[8] = false;
client.sendMiningJob(job);
}*/
});
};

View File

@ -18,7 +18,7 @@ var SubscriptionCounter = function(){
/**
* Defining each client that connects to the stratum server.
* Defining each client that connects to the stratum server.
* Emits:
* - subscription(obj, cback(error, extraNonce1, extraNonce2Size))
* - submit(data(name, jobID, extraNonce2, ntime, nonce))
@ -27,17 +27,13 @@ var StratumClient = function(options){
var pendingDifficulty = null;
//private members
this.socket = options.socket;
this.remoteAddress = options.socket.remoteAddress;
var banning = options.banning;
var _this = this;
this.lastActivity = Date.now();
this.shares = {valid: 0, invalid: 0};
var considerBan = (!banning || !banning.enabled) ? function(){ return false } : function(shareValid){
if (shareValid === true) _this.shares.valid++;
else _this.shares.invalid++;
@ -65,7 +61,7 @@ var StratumClient = function(options){
handleSubscribe(message);
break;
case 'mining.authorize':
handleAuthorize(message, true /*reply to socket*/);
handleAuthorize(message);
break;
case 'mining.submit':
_this.lastActivity = Date.now();
@ -78,6 +74,13 @@ var StratumClient = function(options){
error : true
});
break;
case 'mining.extranonce.subscribe':
sendJson({
id: message.id,
result: false,
error: [20, "Not supported.", null]
});
break;
default:
_this.emit('unknownStratumMethod', message);
break;
@ -85,12 +88,13 @@ var StratumClient = function(options){
}
function handleSubscribe(message){
if (! _this._authorized ) {
if (!_this.authorized) {
_this.requestedSubscriptionBeforeAuth = true;
}
_this.emit('subscription',
{},
function(error, extraNonce1, extraNonce2Size){
function(error, extraNonce1, extraNonce1){
if (error){
sendJson({
id: message.id,
@ -100,35 +104,30 @@ var StratumClient = function(options){
return;
}
_this.extraNonce1 = extraNonce1;
sendJson({
id: message.id,
result: [
[
["mining.set_difficulty", options.subscriptionId],
["mining.notify", options.subscriptionId]
],
extraNonce1,
extraNonce2Size
null, //sessionId
extraNonce1
],
error: null
});
}
);
});
}
function handleAuthorize(message, replyToSocket){
function handleAuthorize(message){
_this.workerName = message.params[0];
_this.workerPass = message.params[1];
options.authorizeFn(_this.remoteAddress, options.socket.localPort, _this.workerName, _this.workerPass, function(result) {
_this.authorized = (!result.error && result.authorized);
if (replyToSocket) {
sendJson({
id : message.id,
result : _this.authorized,
error : result.error
});
}
sendJson({
id : message.id,
result : _this.authorized,
error : result.error
});
// If the authorizer wants us to close the socket lets do it.
if (result.disconnect === true) {
@ -138,7 +137,7 @@ var StratumClient = function(options){
}
function handleSubmit(message){
if (!_this.authorized){
if (_this.authorized === false){
sendJson({
id : message.id,
result: null,
@ -160,9 +159,10 @@ var StratumClient = function(options){
{
name : message.params[0],
jobId : message.params[1],
extraNonce2 : message.params[2],
nTime : message.params[3],
nonce : message.params[4]
nTime : message.params[2],
extraNonce2 : message.params[3],
soln : message.params[4],
nonce : _this.extraNonce1 + message.params[3]
},
function(error, result){
if (!considerBan(result)){
@ -174,7 +174,7 @@ var StratumClient = function(options){
}
}
);
}
function sendJson(){
@ -216,7 +216,7 @@ var StratumClient = function(options){
var messages = dataBuffer.split('\n');
var incomplete = dataBuffer.slice(-1) === '\n' ? '' : messages.pop();
messages.forEach(function(message){
if (message === '') return;
if (message.length < 1) return;
var messageJson;
try {
messageJson = JSON.parse(message);
@ -225,9 +225,9 @@ var StratumClient = function(options){
_this.emit('malformedMessage', message);
socket.destroy();
}
return;
}
if (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.
* returns boolean
**/
this.sendDifficulty = function(difficulty){
if (difficulty === this.difficulty)
return false;
_this.previousDifficulty = _this.difficulty;
_this.difficulty = difficulty;
this.sendDifficulty = function(target){
sendJson({
id : null,
method: "mining.set_difficulty",
params: [difficulty]//[512],
method: "mining.set_target",
params: ["0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"] // FIXME
//params: ["00f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0"] // suprnova
});
return true;
};
@ -278,18 +274,10 @@ var StratumClient = function(options){
var lastActivityAgo = Date.now() - _this.lastActivity;
if (lastActivityAgo > options.connectionTimeout * 1000){
_this.emit('socketTimeout', 'last submitted a share was ' + (lastActivityAgo / 1000 | 0) + ' seconds ago');
_this.socket.destroy();
return;
}
if (pendingDifficulty !== null){
var result = _this.sendDifficulty(pendingDifficulty);
pendingDifficulty = null;
if (result) {
_this.emit('difficultyChanged', _this.difficulty);
}
}
sendJson({
id : null,
method: "mining.notify",
@ -358,7 +346,7 @@ var StratumServer = exports.Server = function StratumServer(options, authorizeFn
var client = new StratumClient(
{
subscriptionId: subscriptionId,
authorizeFn: authorizeFn,
authorizeFn: authorizeFn, //FIXME
socket: socket,
banning: options.banning,
connectionTimeout: options.connectionTimeout,

View File

@ -1,245 +1,86 @@
var bitcoin = require('bitcoinjs-lib');
var util = require('./util.js');
// public members
var txHash;
/*
function Transaction(params){
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/')
]);
}
exports.txHash = function(){
return txHash;
};
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({
inputs: [new TransactionInput({
prevOutIndex : Math.pow(2, 32) - 1,
sigScript : new ScriptSig({
height : rpcData.height,
flags : rpcData.coinbaseaux.flags,
extraNoncePlaceholder : extraNoncePlaceholder
})
})],
outputs: [new TransactionOutput({
value : rpcData.coinbasevalue,
pkScriptBuffer : publicKey
})]
exports.createGeneration = function(blockHeight, reward, recipients, poolAddress){
var tx = new bitcoin.Transaction();
// input for coinbase tx
if (blockHeight.toString(16).length % 2 === 0) {
var blockHeightSerial = blockHeight.toString(16);
} else {
var blockHeightSerial = '0' + blockHeight.toString(16);
}
length = '0' + (blockHeightSerial.length / 2);
var serializedBlockHeight = Buffer.concat([
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();
var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder);
var p1 = txBuffer.slice(0, epIndex);
var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length);
// tx for mining pool
tx.addOutput(
scriptCompile(bitcoin.address.fromBase58Check(poolAddress).hash),
reward * (1 - feePercent)
);
this.transaction = tx;
this.coinbase = [p1, p2];
// tx for recipients
recipients.forEach(function(value){
tx.addOutput(
recipients[value],
reward * recipients[value.fee]
)
});
};
*/
/*
^^^^ 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
]);
// sign with random key
txHex = tx.toHex();
// assign
txHash = tx.getHash().toString('hex');
/*
The generation transaction must be split at the extranonce (which located in the transaction input
scriptSig). Miners send us unique extranonces that we use to join the two parts in attempt to create
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];
console.log('txHex: ' + txHex.toString('hex'));
console.log('txHash: ' + txHash);
*/
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');
exports.addressFromEx = function(exAddress, ripdm160Key){
exports.addressFromEx = function (exAddress, ripdm160Key) {
try {
var versionByte = exports.getVersionByte(exAddress);
var addrBase = Buffer.concat([versionByte, new Buffer(ripdm160Key, 'hex')]);
@ -12,48 +12,48 @@ exports.addressFromEx = function(exAddress, ripdm160Key){
var address = Buffer.concat([addrBase, checksum]);
return base58.encode(address);
}
catch(e){
catch (e) {
return null;
}
};
exports.getVersionByte = function(addr){
exports.getVersionByte = function (addr) {
var versionByte = base58.decode(addr).slice(0, 1);
return versionByte;
};
exports.sha256 = function(buffer){
exports.sha256 = function (buffer) {
var hash1 = crypto.createHash('sha256');
hash1.update(buffer);
return hash1.digest();
};
exports.sha256d = function(buffer){
exports.sha256d = function (buffer) {
return exports.sha256(exports.sha256(buffer));
};
exports.reverseBuffer = function(buff){
exports.reverseBuffer = function (buff) {
var reversed = new Buffer(buff.length);
for (var i = buff.length - 1; i >= 0; i--)
reversed[buff.length - i - 1] = buff[i];
return reversed;
};
exports.reverseHex = function(hex){
exports.reverseHex = function (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);
return exports.reverseBuffer(buff);
};
exports.uint256BufferFromHash = function(hex){
exports.uint256BufferFromHash = function (hex) {
var fromHex = new Buffer(hex, 'hex');
if (fromHex.length != 32){
if (fromHex.length != 32) {
var empty = new Buffer(32);
empty.fill(0);
fromHex.copy(empty);
@ -63,31 +63,31 @@ exports.uint256BufferFromHash = function(hex){
return exports.reverseBuffer(fromHex);
};
exports.hexFromReversedBuffer = function(buffer){
exports.hexFromReversedBuffer = function (buffer) {
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
*/
exports.varIntBuffer = function(n){
exports.varIntBuffer = function (n) {
if (n < 0xfd)
return new Buffer([n]);
else if (n < 0xffff){
else if (n < 0xffff) {
var buff = new Buffer(3);
buff[0] = 0xfd;
buff.writeUInt16LE(n, 1);
return buff;
}
else if (n < 0xffffffff){
else if (n < 0xffffffff) {
var buff = new Buffer(5);
buff[0] = 0xfe;
buff.writeUInt32LE(n, 1);
return buff;
}
else{
else {
var buff = new Buffer(9);
buff[0] = 0xff;
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);
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
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
*/
exports.serializeNumber = function(n){
exports.serializeNumber = function (n) {
/* Old version that is bugged
if (n < 0xfd){
var buff = new Buffer(2);
buff[0] = 0x1;
buff.writeUInt8(n, 1);
return buff;
}
else if (n <= 0xffff){
var buff = new Buffer(4);
buff[0] = 0x3;
buff.writeUInt16LE(n, 1);
return buff;
}
else if (n <= 0xffffffff){
var buff = new Buffer(5);
buff[0] = 0x4;
buff.writeUInt32LE(n, 1);
return buff;
}
else{
return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]);
}*/
if (n < 0xfd){
var buff = new Buffer(2);
buff[0] = 0x1;
buff.writeUInt8(n, 1);
return buff;
}
else if (n <= 0xffff){
var buff = new Buffer(4);
buff[0] = 0x3;
buff.writeUInt16LE(n, 1);
return buff;
}
else if (n <= 0xffffffff){
var buff = new Buffer(5);
buff[0] = 0x4;
buff.writeUInt32LE(n, 1);
return buff;
}
else{
return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]);
}*/
//New version from TheSeven
if (n >= 1 && n <= 16) return new Buffer([0x50 + n]);
var l = 1;
var buff = new Buffer(9);
while (n > 0x7f)
{
while (n > 0x7f) {
buff.writeUInt8(n & 0xff, l++);
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)
return Buffer.concat([
@ -178,33 +177,32 @@ exports.serializeString = function(s){
};
exports.packUInt16LE = function(num){
exports.packUInt16LE = function (num) {
var buff = new Buffer(2);
buff.writeUInt16LE(num, 0);
return buff;
};
exports.packInt32LE = function(num){
exports.packInt32LE = function (num) {
var buff = new Buffer(4);
buff.writeInt32LE(num, 0);
return buff;
};
exports.packInt32BE = function(num){
exports.packInt32BE = function (num) {
var buff = new Buffer(4);
buff.writeInt32BE(num, 0);
return buff;
};
exports.packUInt32LE = function(num){
exports.packUInt32LE = function (num) {
var buff = new Buffer(4);
buff.writeUInt32LE(num, 0);
return buff;
};
exports.packUInt32BE = function(num){
exports.packUInt32BE = function (num) {
var buff = new Buffer(4);
buff.writeUInt32BE(num, 0);
return buff;
};
exports.packInt64LE = function(num){
exports.packInt64LE = function (num) {
var buff = new Buffer(8);
buff.writeUInt32LE(num % Math.pow(2, 32), 0);
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
*/
exports.range = function(start, stop, step){
if (typeof stop === 'undefined'){
exports.range = function (start, stop, step) {
if (typeof stop === 'undefined') {
stop = start;
start = 0;
}
if (typeof step === 'undefined'){
if (typeof step === 'undefined') {
step = 1;
}
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)) {
return [];
}
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);
}
return result;
};
/*
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) {
console.error('Invalid pubkey: ' + key);
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');
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);
if (decoded.length != 25){
if (decoded.length !== 25 && decoded.length !== 26) {
console.error('invalid address length for ' + addr);
throw new Error();
}
if (!decoded){
if (!decoded) {
console.error('base58 decode failed for ' + addr);
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])]);
};
exports.getReadableHashRateString = function(hashrate){
exports.getReadableHashRateString = function (hashrate) {
var i = -1;
var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ];
var byteUnits = [' KH', ' MH', ' GH', ' TH', ' PH'];
do {
hashrate = hashrate / 1024;
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
exports.shiftMax256Right = function(shiftRight){
exports.shiftMax256Right = function (shiftRight) {
//Max value uint256 (an array of ones representing 256 enabled bits)
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
var octets = [];
for (var i = 0; i < 32; i++){
for (var i = 0; i < 32; i++) {
octets[i] = 0;
@ -317,7 +311,7 @@ exports.shiftMax256Right = function(shiftRight){
var bits = arr256.slice(i * 8, i * 8 + 8);
//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);
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 buff = bigNum.toBuffer();
buff = buff.readUInt8(0) > 0x7f ? Buffer.concat([new Buffer([0x00]), buff]) : buff;
buff = Buffer.concat([new Buffer([buff.length]), buff]);
var compact = buff.slice(0, 4);
return compact;
return compact = buff.slice(0, 4);
};
/*
@ -344,25 +337,25 @@ exports.bufferToCompactBits = function(startingBuff){
More info: https://en.bitcoin.it/wiki/Target
*/
exports.bignumFromBitsBuffer = function(bitsBuff){
exports.bignumFromBitsBuffer = function (bitsBuff) {
var numBytes = bitsBuff.readUInt8(0);
var bigBits = bignum.fromBuffer(bitsBuff.slice(1));
var target = bigBits.mul(
bignum(2).pow(
bignum(8).mul(
numBytes - 3
numBytes - 3
)
)
);
return target;
};
exports.bignumFromBitsHex = function(bitsString){
exports.bignumFromBitsHex = function (bitsString) {
var bitsBuff = new Buffer(bitsString, 'hex');
return exports.bignumFromBitsBuffer(bitsBuff);
};
exports.convertBitsToBuff = function(bitsBuff){
exports.convertBitsToBuff = function (bitsBuff) {
var target = exports.bignumFromBitsBuffer(bitsBuff);
var resultBuff = target.toBuffer();
var buff256 = new Buffer(32);
@ -371,6 +364,6 @@ exports.convertBitsToBuff = function(bitsBuff){
return buff256;
};
exports.getTruncatedDiff = function(shift){
exports.getTruncatedDiff = function (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
*/
function RingBuffer(maxSize){
function RingBuffer(maxSize) {
var data = [];
var cursor = 0;
var isFull = false;
this.append = function(x){
if (isFull){
this.append = function (x) {
if (isFull) {
data[cursor] = x;
cursor = (cursor + 1) % maxSize;
}
else{
else {
data.push(x);
cursor++;
if (data.length === maxSize){
if (data.length === maxSize) {
cursor = 0;
isFull = true;
}
}
};
this.avg = function(){
var sum = data.reduce(function(a, b){ return a + b });
this.avg = function () {
var sum = data.reduce(function (a, b) {
return a + b
});
return sum / (isFull ? maxSize : cursor);
};
this.size = function(){
this.size = function () {
return isFull ? maxSize : cursor;
};
this.clear = function(){
this.clear = function () {
data = [];
cursor = 0;
isFull = false;
@ -45,7 +47,7 @@ function toFixed(num, 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 bufferSize, tMin, tMax;
@ -54,14 +56,13 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
var variance = varDiffOptions.targetTime * (varDiffOptions.variancePercent / 100);
bufferSize = varDiffOptions.retargetTime / varDiffOptions.targetTime * 4;
tMin = varDiffOptions.targetTime - variance;
tMax = varDiffOptions.targetTime + variance;
tMin = varDiffOptions.targetTime - variance;
tMax = varDiffOptions.targetTime + variance;
this.manageClient = function(client){
this.manageClient = function (client) {
var stratumPort = client.socket.localPort;
@ -74,11 +75,11 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
var lastRtc;
var timeBuffer;
client.on('submit', function(){
client.on('submit', function () {
var ts = (Date.now() / 1000) | 0;
if (!lastRtc){
if (!lastRtc) {
lastRtc = ts - options.retargetTime / 2;
lastTs = ts;
timeBuffer = new RingBuffer(bufferSize);
@ -113,7 +114,7 @@ var varDiff = module.exports = function varDiff(port, varDiffOptions){
ddiff = diffMax / client.difficulty;
}
}
else{
else {
return;
}

View File

@ -12,9 +12,9 @@
"litecoin",
"scrypt"
],
"homepage": "https://github.com/zone117x/node-stratum-pool",
"homepage": "https://github.com/joshuayabut/node-stratum-pool",
"bugs": {
"url": "https://github.com/zone117x/node-stratum-pool/issues"
"url": "https://github.com/joshuayabut/node-stratum-pool/issues"
},
"license": "GPL-2.0",
"author": "Matthew Little",
@ -25,13 +25,16 @@
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "https://github.com/zone117x/node-stratum-pool.git"
"url": "https://github.com/joshuayabut/node-stratum-pool.git"
},
"dependencies": {
"multi-hashing": "git://github.com/zone117x/node-multi-hashing.git",
"multi-hashing": "https://github.com/joshuayabut/node-multi-hashing.git",
"bignum": "*",
"base58-native": "*",
"async": "*"
"async": "*",
"merkle-bitcoin": "https://github.com/joshuayabut/merkle-bitcoin.git",
"bitcoinjs-lib": "*",
"promise": "*"
},
"engines": {
"node": ">=0.10"