Added todolist

code cleanup
Added jobManager.blockHashHex which should generate the expected block hash but it seems to not work properly
Moved check for the share difficulty after the block check since it's useless to make the check there.
Changed emit event from client to client.connected and added client.disconnected
Changed share emit data. Now we pass the client instance to everyone listening to the share event.
Added mining.get_transaction to the handled events of stratumclient. It looks like bfgminer uses this.
changed StratumClient.sendDifficulty to StratumCLient.sendAndSetDifficulty which will also perform a check on the given difficulty and broadcasts the sendDifficulty message to the client Only if the new difficulty is different from the previous one.
Code cleanup and comments
This commit is contained in:
Andrea Baccega 2014-01-13 16:17:38 +01:00
parent 263972fcdc
commit a42fe87723
7 changed files with 131 additions and 67 deletions

11
TODOLIST.md Normal file
View File

@ -0,0 +1,11 @@
=== TODO ===
* vekexasia: handle socket error from client so that we could clean up stratumServers Client
* vekexasia: fix check of difficulty of the shares. Not sure why a lot of shares are marked as "low-submit-share"
=== TOFIX ===
* vekexasia: jobManager.js has implement the expected block hash after submitting it to the daemon. This will let us check if the block was accepted by the daemon.
=== DONE ===

View File

@ -1,8 +1,7 @@
var net = require('net'); var net = require('net');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var pool = require('../index.js');
var pool = require('../index.js');
var ShareManager = require('./shareManager.js').ShareManager; var ShareManager = require('./shareManager.js').ShareManager;
var logRef = console.log; var logRef = console.log;
@ -48,7 +47,7 @@ fs.readdir(confFolder, function(err, files){
console.log('Starting pool for ' + coin.options.name); console.log('Starting pool for ' + coin.options.name);
coin.pool = new pool(coin, authorizeFN ); coin.pool = new pool(coin, authorizeFN );
coin.shareManager = new ShareManager(coin.pool); var shareManager = new ShareManager(coin.pool);
coins.push(coin); coins.push(coin);
@ -75,7 +74,7 @@ fs.readdir(confFolder, function(err, files){
if (config.blockNotifyListener.enabled){ if (config.blockNotifyListener.enabled){
console.log("ENABLED"); console.log("blockNotifyListener 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 = '';

View File

@ -11,28 +11,27 @@ var ShareManager = exports.ShareManager = function(pool) {
pool.on('share', function(isValid, data) { pool.on('share', function(isValid, data) {
if (isValid) { if (isValid) {
handleValidShare( handleValidShare(
data.workerName, data.client,
data.blockHeaderHex, data.blockHeaderHex,
data.jobId, data.jobId,
data.clientDifficulty,
data.extraNonce1, data.extraNonce1,
data.extraNonce2, data.extraNonce2,
data.nTime, data.nTime,
data.nonce); data.nonce);
} else { } else {
handleInvalidShare( handleInvalidShare(
data.workerName, data.client,
data.error[0], data.error[0],
data.error[1]); data.error[1]);
} }
}); });
function handleValidShare(workerName, headerHex, jobId, clientDifficulty, extraNonce1, extraNonce2, nTime, nonce) { function handleValidShare(client, headerHex, jobId, extraNonce1, extraNonce2, nTime, nonce) {
console.log("A new Valid share from "+workerName+" has arrived! - "+headerHex); console.log("A new Valid share from "+client.workerName+" has arrived! - "+headerHex);
} }
function handleInvalidShare(workerName, errorCode, errorDescription) { function handleInvalidShare(client, errorCode, errorDescription) {
console.log("Invalid share form "+workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription); console.log("Invalid share form "+client.workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription);
} }
}; };

View File

@ -17,7 +17,6 @@ var pool = module.exports = function pool(coin, authFn){
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
@ -34,9 +33,6 @@ var pool = module.exports = function pool(coin, authFn){
process.exit("ERROR: an authorize Function is needed.") process.exit("ERROR: an authorize Function is needed.")
} }
this.jobManager.on('newBlock', function(blockTemplate){ this.jobManager.on('newBlock', function(blockTemplate){
if ( typeof(_this.stratumServer ) === 'undefined') { if ( typeof(_this.stratumServer ) === 'undefined') {
console.warn("Stratum server still not started! cannot broadcast block!"); console.warn("Stratum server still not started! cannot broadcast block!");
@ -128,7 +124,7 @@ var pool = module.exports = function pool(coin, authFn){
_this.stratumServer.on('started', function(){ _this.stratumServer.on('started', function(){
_this.emit('started'); _this.emit('started');
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.connected', function(client){
client.on('subscription', function(params, resultCallback){ client.on('subscription', function(params, resultCallback){
var extraNonce = _this.jobManager.extraNonceCounter.next(); var extraNonce = _this.jobManager.extraNonceCounter.next();
@ -155,17 +151,16 @@ var pool = module.exports = function pool(coin, authFn){
if (result.error){ if (result.error){
resultCallback(result.error); resultCallback(result.error);
_this.emit('share', false, { _this.emit('share', false, {
workerName : params.name, client : client,
error : result.error error : result.error
}); });
} else { } else {
resultCallback(null, true); resultCallback(null, true);
_this.emit('share', true, { _this.emit('share', true, {
client : client,
blockHeaderHex : result.headerHEX, blockHeaderHex : result.headerHEX,
workerName : params.name, workerName : params.name,
jobId : params.jobId, jobId : params.jobId,
clientDifficulty : client.difficulty,
extraNonce1 : client.extraNonce1,
extraNonce2 : params.extraNonce2, extraNonce2 : params.extraNonce2,
nTime : params.nTime, nTime : params.nTime,
nonce : params.nonce nonce : params.nonce

View File

@ -106,6 +106,25 @@ var JobManager = module.exports = function JobManager(options){
} }
})(); })();
/**
* Tries to estimate the resulting block hash
* This is only valid for scrypt apparently.
* @author vekexasia
**/
function blockHashHex(headerBuffer) {
var result = new Buffer(80);
for (var i=0; i<20; i++) {
for (var j=0; j<4; j++) {
result[i*4+j] = headerBuffer[i*4+3-j];
}
}
var shaed = util.reverseBuffer(util.doublesha(result));
return shaed.toString('hex'); // return the expected block hash
}
//public members //public members
this.extraNonceCounter = new ExtraNonceCounter(); this.extraNonceCounter = new ExtraNonceCounter();
@ -131,45 +150,55 @@ var JobManager = module.exports = function JobManager(options){
return {error: [20, 'incorrect size of extranonce2', null]}; return {error: [20, 'incorrect size of extranonce2', null]};
var job = jobs[jobId]; var job = jobs[jobId];
if (!job) if (!job) {
return {error: [21, 'job not found', null]}; return {error: [21, 'job not found', null]};
}
if (nTime.length !== 8) if (nTime.length !== 8) {
return {error: [20, 'incorrect size of ntime']}; return {error: [20, 'incorrect size of ntime']};
}
var nTimeInt = parseInt(nTime, 16); var nTimeInt = parseInt(nTime, 16);
if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) if (nTimeInt < job.rpcData.curtime || nTime > submitTime + 7200) {
return {error: [20, 'ntime out of range', null]}; return {error: [20, 'ntime out of range', null]};
}
if (nonce.length !== 8) if (nonce.length !== 8) {
return {error: [20, 'incorrect size of nonce']}; return {error: [20, 'incorrect size of nonce']};
}
if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) if (!job.registerSubmit(extraNonce1, extraNonce2, nTime, nonce)) {
return {error: [22, 'duplicate share', null]}; return {error: [22, 'duplicate share', null]};
}
var extraNonce1Buffer = new Buffer(extraNonce1, 'hex'); var extraNonce1Buffer = new Buffer(extraNonce1, 'hex');
var extraNonce2Buffer = new Buffer(extraNonce2, 'hex'); var extraNonce2Buffer = new Buffer(extraNonce2, 'hex');
var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer); var coinbaseBuffer = job.serializeCoinbase(extraNonce1Buffer, extraNonce2Buffer);
var coinbaseHash = util.doublesha(coinbaseBuffer); var coinbaseHash = util.doublesha(coinbaseBuffer);
var merkleRoot = job.merkleTree.withFirst(coinbaseHash); var merkleRoot = job.merkleTree.withFirst(coinbaseHash);
merkleRoot = util.reverseBuffer(merkleRoot).toString('hex'); merkleRoot = util.reverseBuffer(merkleRoot).toString('hex');
var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce); var headerBuffer = job.serializeHeader(merkleRoot, nTime, nonce);
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});
var targetUser = bignum(diffDividend / difficulty);
if (headerBigNum.gt(targetUser)){
return {error: [23, 'low difficulty share', null]};
}
if (job.target.ge(headerBigNum)){ if (job.target.ge(headerBigNum)){
var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer);
console.log("EXPECTED BLOCK HASH: "+blockHashHex(headerBuffer)); // NOT WORKING :(?
_this.emit('blockFound', blockBuf.toString('hex')); _this.emit('blockFound', blockBuf.toString('hex'));
} else {
// If block is not found we want also to check the difficulty of the share.
// TODO: this seems to not be working properly
var targetUser = bignum(diffDividend / difficulty);
if (headerBigNum.gt(targetUser)){
return {error: [23, 'low difficulty share', null]};
}
} }
return {result: true, headerHEX: headerBigNum.toString(16)}; return {result: true, headerHEX: headerBigNum.toString(16)};

View File

@ -46,8 +46,15 @@ var StratumClient = function(options){
case 'mining.submit': case 'mining.submit':
handleSubmit(message); handleSubmit(message);
break; break;
case 'mining.get_transactions':
sendJson({
id : null,
result : [],
error : true
});
break;
default: default:
console.dir('unknown stratum client message: ' + message); console.dir('unknown stratum client message: ' + JSON.stringify(message));
break; break;
} }
} }
@ -82,10 +89,10 @@ var StratumClient = function(options){
} }
function handleAuthorize(message){ function handleAuthorize(message){
this.workerIP = options.socket.address().address; _this.workerIP = options.socket.address().address;
this.workerName = message.params[0]; _this.workerName = message.params[0];
this.workerPass = message.params[1]; _this.workerPass = message.params[1];
options.authorizeFn(this.workerIP, this.workerName, this.workerPass, function(err, authorized, shouldCloseSocket, difficulty) { options.authorizeFn(_this.workerIP, _this.workerName, _this.workerPass, function(err, authorized, shouldCloseSocket, difficulty) {
_this.authorized = ( ! err && authorized ); _this.authorized = ( ! err && authorized );
sendJson({ sendJson({
id : message.id, id : message.id,
@ -105,12 +112,12 @@ var StratumClient = function(options){
console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err)); console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err));
options.socket.end(); options.socket.end();
} else { } else {
_this.sendDifficulty(diff); _this.sendAndSetDifficultyIfNew(diff);
} }
}); });
} else if (typeof(difficulty) === 'number') { } else if (typeof(difficulty) === 'number') {
_this.sendDifficulty(difficulty); _this.sendAndSetDifficultyIfNew(difficulty);
} else { } else {
process.exit("Difficulty from authorizeFn callback is neither a function or a number"); process.exit("Difficulty from authorizeFn callback is neither a function or a number");
} }
@ -140,7 +147,6 @@ 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],
@ -164,7 +170,6 @@ var StratumClient = function(options){
for (var i = 0; i < arguments.length; i++){ for (var i = 0; i < arguments.length; i++){
response += JSON.stringify(arguments[i]) + '\n'; response += JSON.stringify(arguments[i]) + '\n';
} }
console.log('response: ' + response);
options.socket.write(response); options.socket.write(response);
} }
@ -186,8 +191,9 @@ var StratumClient = function(options){
catch(e){ catch(e){
console.log('could not parse stratum client socket message: ' + message); console.log('could not parse stratum client socket message: ' + message);
} }
if (messageJson) if (messageJson) {
handleMessage(messageJson); handleMessage(messageJson);
}
}); });
dataBuffer = ''; dataBuffer = '';
} }
@ -205,14 +211,28 @@ var StratumClient = function(options){
//public members //public members
this.sendDifficulty = function(difficulty){ /**
_this.difficulty = difficulty; * IF the given difficulty is valid and new it'll send it to the client.
console.log("SENDING DIFFICULTY "+difficulty); * returns boolean
sendJson({ **/
id : null, this.sendAndSetDifficultyIfNew = function(difficulty){
method: "mining.set_difficulty", if (typeof(difficulty) != 'number') {
params: [difficulty]//[512], console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']');
}); return false;
}
if (difficulty !== this.difficulty) {
this.difficulty = difficulty;
sendJson({
id : null,
method: "mining.set_difficulty",
params: [difficulty]//[512],
});
return true;
} else {
return false;
}
}; };
this.sendMiningJob = function(jobParams){ this.sendMiningJob = function(jobParams){
@ -227,6 +247,13 @@ StratumClient.prototype.__proto__ = events.EventEmitter.prototype;
/**
* The actual stratum server.
* It emits the following Events:
* - 'client.connected'(StratumClientInstance) - when a new miner connects
* - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore.
* - 'started' - when the server is up and running
**/
var StratumServer = exports.Server = function StratumServer(options){ var StratumServer = exports.Server = function StratumServer(options){
//private members //private members
@ -247,8 +274,8 @@ var StratumServer = exports.Server = function StratumServer(options){
} }
); );
stratumClients[subscriptionId] = client; stratumClients[subscriptionId] = client;
_this.emit('client', client); _this.emit('client.connected', client);
c.on('disconnect', function() { client.on('socketDisconnect', function() {
delete stratumClients[subscriptionId]; delete stratumClients[subscriptionId];
_this.emit('client.disconnected', client); _this.emit('client.disconnected', client);
}); });
@ -261,9 +288,13 @@ 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) // if a client gets disconnected WHILE doing this loop a crash might happen.
// 'm not sure if that can ever happn but an if here doesn't hurt!
if (typeof(stratumClients[clientId]) !== 'undefined') {
stratumClients[clientId].sendMiningJob(jobParams);
}
} }
}; };
}; };

View File

@ -86,23 +86,23 @@ var Generation = exports.Generation = function Generation(rpcData, publicKey, ex
var tx = new Transaction({ var tx = new Transaction({
inputs: [new TransactionInput({ inputs: [new TransactionInput({
prevOutIndex: Math.pow(2, 32) - 1, prevOutIndex : Math.pow(2, 32) - 1,
sigScript: new ScriptSig({ sigScript : new ScriptSig({
height: rpcData.height, height : rpcData.height,
flags: rpcData.coinbaseaux.flags, flags : rpcData.coinbaseaux.flags,
extraNoncePlaceholder: extraNoncePlaceholder extraNoncePlaceholder : extraNoncePlaceholder
}) })
})], })],
outputs: [new TransactionOutput({ outputs: [new TransactionOutput({
value: rpcData.coinbasevalue, value : rpcData.coinbasevalue,
pkScriptBuffer: publicKey pkScriptBuffer : publicKey
})] })]
}); });
var txBuffer = tx.toBuffer(); var txBuffer = tx.toBuffer();
var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder); var epIndex = buffertools.indexOf(txBuffer, extraNoncePlaceholder);
var p1 = txBuffer.slice(0, epIndex); var p1 = txBuffer.slice(0, epIndex);
var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length); var p2 = txBuffer.slice(epIndex + extraNoncePlaceholder.length);
this.transaction = tx; this.transaction = tx;
this.coinbase = [p1, p2]; this.coinbase = [p1, p2];