This commit is contained in:
Matthew Little 2014-01-13 14:17:56 -05:00
commit 677bc176a4
9 changed files with 185 additions and 44 deletions

View File

@ -6,7 +6,7 @@ var transactions = require('./transactions.js');
var util = require('./util.js'); var util = require('./util.js');
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publicKey, reward, extraNoncePlaceholder){ var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){
//private members //private members
@ -29,7 +29,7 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ
//public members //public members
this.rpcData = rpcData; this.rpcData = rpcData;
this.jobId = jobId; this.jobId = null;
this.target = util.bignumFromBits(rpcData.bits); this.target = util.bignumFromBits(rpcData.bits);
this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex'); this.prevHashReversed = util.reverseByteOrder(new Buffer(rpcData.previousblockhash, 'hex')).toString('hex');
this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){ this.transactionData = Buffer.concat(rpcData.transactions.map(function(tx){
@ -40,10 +40,13 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ
this.generationTransaction = new transactions.Generation( this.generationTransaction = new transactions.Generation(
rpcData, rpcData,
publicKey, publicKey,
reward,
extraNoncePlaceholder extraNoncePlaceholder
); );
this.setJobId = function (jobId) {
this.jobId = jobId;
}
this.serializeCoinbase = function(extraNonce1, extraNonce2){ this.serializeCoinbase = function(extraNonce1, extraNonce2){
return Buffer.concat([ return Buffer.concat([
this.generationTransaction.coinbase[0], this.generationTransaction.coinbase[0],
@ -71,7 +74,7 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publ
this.serializeBlock = function(header, coinbase){ this.serializeBlock = function(header, coinbase){
return Buffer.concat([ return Buffer.concat([
header, header,
util.varIntBuffer(this.rpcData.transaction.length + 1), util.varIntBuffer(this.rpcData.transactions.length + 1),
coinbase, coinbase,
this.transactionData this.transactionData
]); ]);

View File

@ -3,15 +3,15 @@
"symbol": "doge", "symbol": "doge",
"algorithm": "scrypt", "algorithm": "scrypt",
"reward": "POW", "reward": "POW",
"address": "DDt79i6P3Wro3SD3HSnkRLpMgUGUGdiNhS", "address": "mkLyYQ5U8aFQ8aDAF1GS9zXStTzi3m29Tg",
"stratumPort": 3334, "stratumPort": 3334,
"difficulty": 8, "difficulty": 8,
"templateRefreshInterval": 60, "templateRefreshInterval": 60,
"merkleRefreshInterval": 60, "merkleRefreshInterval": 60,
"daemon": { "daemon": {
"host": "localhost", "host": "localhost",
"port": 8332, "port": 19334,
"user": "test", "user": "testnet",
"password": "test" "password": "AHhQYqfSZqzQvkSXAtHtDAbKaZaoPih3wfmJfgCtjRx9"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"blockNotifyListener": { "blockNotifyListener": {
"enabled": true, "enabled": false,
"port": 8117, "port": 8117,
"password": "test" "password": "test"
} }

View File

@ -55,6 +55,11 @@ function DaemonInterface(options){
params: params params: params
}); });
if (method == 'submitblock') {
console.log("SUBMITBLOCK daemon");
console.log(requestJson);
}
var options = { var options = {
hostname: 'localhost', hostname: 'localhost',
port: _this.options.port, port: _this.options.port,

24
init.js
View File

@ -3,6 +3,7 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var pool = require('./pool.js'); var pool = require('./pool.js');
var ShareManager = require('./shareManager.js').ShareManager;
var logRef = console.log; var logRef = console.log;
console.log = function(s){ console.log = function(s){
@ -33,8 +34,28 @@ fs.readdir(confFolder, function(err, files){
var coinJson = JSON.parse(data) var coinJson = JSON.parse(data)
var coin = new Coin(coinJson); var coin = new Coin(coinJson);
console.log('Starting pool for ' + coin.options.name); console.log('Starting pool for ' + coin.options.name);
coin.pool = new pool(coin); coin.pool = new pool(coin);
coin.shareManager = new ShareManager(coin.pool);
coins.push(coin); coins.push(coin);
// If the block notify listener is not enabled lets set up the polling.
if ( ! config.blockNotifyListener.enabled ) {
// as soon as the pool is started we start polling
var pollingTime = typeof(config.blockPollingTime) === 'undefined' ? 5000 : parseInt(config.blockPollingTime, 10);
coin.pool.on('started', function() {
var curPool = this;
setInterval(
function() {
curPool.processBlockPolling();
},
pollingTime
);
});
}
}); });
}); });
@ -42,6 +63,7 @@ fs.readdir(confFolder, function(err, files){
if (config.blockNotifyListener.enabled){ if (config.blockNotifyListener.enabled){
console.log("ENABLED");
var blockNotifyServer = net.createServer(function(c) { var blockNotifyServer = net.createServer(function(c) {
console.log('server connected'); console.log('server connected');
var data = ''; var data = '';
@ -69,4 +91,4 @@ if (config.blockNotifyListener.enabled){
}); });
}); });
blockNotifyServer.listen(config.blockNotifyListener.port, function() {}); blockNotifyServer.listen(config.blockNotifyListener.port, function() {});
} }

View File

@ -36,6 +36,10 @@ var JobCounter = function(){
counter++; counter++;
if (counter % 0xffff === 0) if (counter % 0xffff === 0)
counter = 1; counter = 1;
return this.cur();
};
this.cur = function () {
return counter.toString(16); return counter.toString(16);
}; };
}; };
@ -50,15 +54,19 @@ var JobManager = module.exports = function JobManager(options){
var jobs = {}; var jobs = {};
/**
* It only checks if the blockTemplate is already in our jobs list.
* @returns true if it's a new block, false otherwise.
* used by onNewTemplate
**/
function CheckNewIfNewBlock(blockTemplate){ function CheckNewIfNewBlock(blockTemplate){
var newBlock = true; var newBlock = true;
for(var job in jobs){ for(var job in jobs){
if (jobs[job].rpcData.previousblockhash === blockTemplate.rpcData.previousblockhash) if (jobs[job].rpcData.previousblockhash === blockTemplate.rpcData.previousblockhash) {
newBlock = false; newBlock = false;
}
} }
if (newBlock) return newBlock;
_this.emit('newBlock', blockTemplate);
} }
var diffDividend = (function(){ var diffDividend = (function(){
@ -102,9 +110,14 @@ var JobManager = module.exports = function JobManager(options){
this.currentJob; this.currentJob;
this.newTemplate = function(rpcData, publicKey){ this.newTemplate = function(rpcData, publicKey){
this.currentJob = new blockTemplate(jobCounter.next(), rpcData, publicKey, _this.extraNoncePlaceholder); var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder);
jobs[this.currentJob.jobId] = this.currentJob; if ( CheckNewIfNewBlock(tmpBlockTemplate) ) {
CheckNewIfNewBlock(this.currentJob); tmpBlockTemplate.setJobId(jobCounter.next());
jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate;
this.currentJob = jobs[tmpBlockTemplate.jobId];
_this.emit('newBlock', tmpBlockTemplate);
}
}; };
this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){ this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){
@ -144,17 +157,18 @@ var JobManager = module.exports = function JobManager(options){
var headerHash = hashDigest(headerBuffer, nTimeInt); var headerHash = hashDigest(headerBuffer, nTimeInt);
var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32}); var headerBigNum = bignum.fromBuffer(headerHash, {endian: 'little', size: 32});
if (job.target.ge(headerBigNum)){
var blockHex = job.serializeBlock(headerBuffer, coinbaseBuffer);
_this.emit('blockFound', blockHex);
}
var targetUser = bignum(diffDividend / difficulty); var targetUser = bignum(diffDividend / difficulty);
if (headerBigNum.gt(targetUser)){ if (headerBigNum.gt(targetUser)){
return {error: [23, 'low difficulty share', null]}; return {error: [23, 'low difficulty share', null]};
} }
return {result: true}; if (job.target.ge(headerBigNum)){
var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer);
_this.emit('blockFound', blockBuf.toString('hex'), headerBigNum.toString(16), coinbaseHash.toString('hex'));
}
return {result: true, headerHEX: headerBigNum.toString(16)};
}; };
}; };
JobManager.prototype.__proto__ = events.EventEmitter.prototype; JobManager.prototype.__proto__ = events.EventEmitter.prototype;

87
pool.js
View File

@ -9,35 +9,46 @@ var stratum = require('./stratum.js');
var jobManager = require('./jobManager.js'); var jobManager = require('./jobManager.js');
var util = require('./util.js'); var util = require('./util.js');
var pool = module.exports = function pool(coin){ var pool = module.exports = function pool(coin){
var _this = this; var _this = this;
var publicKeyBuffer; var publicKeyBuffer;
this.shareManager = undefined; // just for us to know that the variable should be this one.
this.jobManager = new jobManager({ this.jobManager = new jobManager({
algorithm: coin.options.algorithm, algorithm: coin.options.algorithm,
address: coin.options.address address: coin.options.address
}); });
this.jobManager.on('newBlock', function(blockTemplate){ this.jobManager.on('newBlock', function(blockTemplate){
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams()); if ( typeof(_this.stratumServer ) === 'undefined') {
}).on('blockFound', function(blockHex){ console.warn("Stratum server still not started! cannot broadcast block!");
} else {
if (coin.options.hasSubmitMethod) _this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
}
}).on('blockFound', function(blockHex, headerHex, third){
console.log("BLOCK "+blockHex);
console.log("HEADER "+headerHex);
console.log("THIRD "+third);
if (coin.options.hasSubmitMethod) {
_this.daemon.cmd('submitblock', _this.daemon.cmd('submitblock',
[blockHex], [blockHex],
function(error, result){ function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblock", JSON.stringify(error), JSON.stringify(result));
} }
); );
else } else {
_this.daemon.cmd('getblocktemplate', _this.daemon.cmd('getblocktemplate',
[{'mode': 'submit', 'data': blockHex}], [{'mode': 'submit', 'data': blockHex}],
function(error, result){ function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result));
} }
); );
}
}); });
console.log('Connecting to daemon for ' + coin.options.name); console.log('Connecting to daemon for ' + coin.options.name);
@ -84,7 +95,7 @@ var pool = module.exports = function pool(coin){
_this.jobManager.newTemplate(results.rpcTemplate, publicKeyBuffer); _this.jobManager.newTemplate(results.rpcTemplate, publicKeyBuffer);
StartStatumServer(); StartStratumServer();
}); });
@ -93,13 +104,17 @@ var pool = module.exports = function pool(coin){
}); });
function StartStatumServer(){ function StartStratumServer(){
console.log('Stratum server starting on port ' + coin.options.stratumPort + ' for ' + coin.options.name); console.log('Stratum server starting on port ' + coin.options.stratumPort + ' for ' + coin.options.name);
_this.stratumServer = new stratum.Server({ _this.stratumServer = new stratum.Server({
port: coin.options.stratumPort port: coin.options.stratumPort
}); });
_this.stratumServer.on('started', function(){ _this.stratumServer.on('started', function(){
<<<<<<< HEAD
=======
_this.emit('started');
>>>>>>> a301c49943980d0209fb42114999099eeeb44ded
console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name); console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name);
}).on('client', function(client){ }).on('client', function(client){
client.on('subscription', function(params, resultCallback){ client.on('subscription', function(params, resultCallback){
@ -110,7 +125,11 @@ var pool = module.exports = function pool(coin){
extraNonce2Size extraNonce2Size
); );
this.sendDifficulty(coin.options.difficulty); this.sendDifficulty(coin.options.difficulty);
this.sendMiningJob(_this.jobManager.currentJob.getJobParams()); if (typeof(_this.jobManager.currentJob) === 'undefined') {
console.warn("[subscription] Cannot send job to client. No jobs in jobManager!");
} else {
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
}
}).on('authorize', function(params, resultCallback){ }).on('authorize', function(params, resultCallback){
resultCallback(null, true); resultCallback(null, true);
}).on('submit', function(params, resultCallback){ }).on('submit', function(params, resultCallback){
@ -124,9 +143,24 @@ var pool = module.exports = function pool(coin){
); );
if (result.error){ if (result.error){
resultCallback(result.error); resultCallback(result.error);
return; _this.emit('share', false, {
workerName : params.name,
error : result.error
});
} else {
resultCallback(null, true);
_this.emit('share', true, {
blockHeaderHex : result.headerHEX,
workerName : params.name,
jobId : params.jobId,
clientDifficulty : client.difficulty,
extraNonce1 : client.extraNonce1,
extraNonce2 : params.extraNonce2,
nTime : params.nTime,
nonce : params.nonce
});
} }
resultCallback(null, true);
}); });
}); });
} }
@ -135,22 +169,37 @@ var pool = module.exports = function pool(coin){
_this.daemon.cmd('getblocktemplate', _this.daemon.cmd('getblocktemplate',
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
function(error, result){ function(error, result){
if (error){ if (error) {
console.log('getblocktemplate rpc error for ' + coin.options.name);
callback(error); callback(error);
} } else {
else{
callback(null, result); callback(null, result);
} }
} }
); );
} }
/**
* This method needs to be called to perform a block polling to the daemon so that we can notify our miners
* about new blocks
**/
this.processBlockPolling = function() {
GetBlockTemplate(function(error, result) {
if (error) {
console.error("[processBlockPolling] Error getting block template for " + coin.options.name);
}
_this.jobManager.newTemplate(result, publicKeyBuffer);
});
}
/**
* 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){ this.processBlockNotify = function(blockHash){
if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){ if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
GetBlockTemplate(function(error, result){ GetBlockTemplate(function(error, result){
if (error){ if (error){
console.log('Error getting block template for ' + coin.options.name); console.error('[processBlockNotify] Error getting block template for ' + coin.options.name);
return; return;
} }
_this.jobManager.newTemplate(result, publicKeyBuffer); _this.jobManager.newTemplate(result, publicKeyBuffer);

View File

@ -0,0 +1,41 @@
var events = require('events');
/**
* This ShareManager Events table: will emit the following events:
*
* LISTENS on:
* - pool('share')
**/
var ShareManager = exports.ShareManager = function(pool) {
pool.on('share', function(isValid, data) {
if (isValid) {
handleValidShare(
data.workerName,
data.blockHeaderHex,
data.jobId,
data.clientDifficulty,
data.extraNonce1,
data.extraNonce2,
data.nTime,
data.nonce);
} else {
handleInvalidShare(
data.workerName,
data.error[0],
data.error[1]);
}
});
function handleValidShare(workerName, headerHex, jobId, clientDifficulty, extraNonce1, extraNonce2, nTime, nonce) {
console.log("A new Valid share from "+workerName+" has arrived! - "+headerHex);
}
function handleInvalidShare(workerName, errorCode, errorDescription) {
console.log("Invalid share form "+workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription);
}
};
ShareManager.prototype.__proto__ = events.EventEmitter.prototype;

View File

@ -79,6 +79,12 @@ var StratumClient = function(options){
}, },
function(error, result){ function(error, result){
_this.authorized = result; _this.authorized = result;
/*if (_this.authorized) {
// if authorized lets store the workername
// so that when a share is found we can get it for the accounting
_this.workerName = message.params[0][0];
}*/
sendJson({ sendJson({
id: message.id, id: message.id,
result: result, result: result,
@ -105,6 +111,7 @@ var StratumClient = function(options){
}); });
return; return;
} }
console.log("SUBMIT "+JSON.stringify(message));
_this.emit('submit', _this.emit('submit',
{ {
name: message.params[0], name: message.params[0],
@ -214,7 +221,7 @@ var StratumServer = exports.Server = function StratumServer(options){
//public members //public members
this.broadcastMiningJobs = function(jobParams){ this.broadcastMiningJobs = function(jobParams){
for (var clientId in _stratumClients){ for (var clientId in stratumClients){
stratumClients[clientId].sendMiningJob(jobParams) stratumClients[clientId].sendMiningJob(jobParams)
} }
}; };