Updated structure

This commit is contained in:
Matthew Little 2014-01-13 17:45:10 -07:00
parent 2b5aefd7b4
commit cea68b771f
18 changed files with 1521 additions and 1499 deletions

View File

@ -32,16 +32,6 @@ Requirements
------------ ------------
* node v0.10+ * node v0.10+
* coin daemon * coin daemon
* PostgreSQL
* npm dependencies
* [scrypt256-hash](https://github.com/zone117x/node-scrypt256-hash)
* [scrypt-jane-hash](https://github.com/zone117x/node-scrypt-jane-hash)
* [quark-hash](https://github.com/zone117x/node-quark-hash)
* [binpack](https://github.com/russellmcc/node-binpack)
* [bignum](https://github.com/justmoon/node-bignum)
* [buffertools] (https://github.com/bnoordhuis/node-buffertools)
* [base58-native](https://github.com/gasteve/node-base58)
* [async](https://github.com/caolan/async)
Installation Installation
@ -98,7 +88,7 @@ Installation
* To start the poolserver run: * To start the poolserver run:
```bash ```bash
node init.js node index.js
``` ```

View File

@ -6,13 +6,11 @@
"address": "n3s8iDk1onxyY2nuC1k4HoRQFGJ7BhjFcq", "address": "n3s8iDk1onxyY2nuC1k4HoRQFGJ7BhjFcq",
"stratumPort": 3334, "stratumPort": 3334,
"difficulty": 8, "difficulty": 8,
"blockRefreshInterval": 1, "blockRefreshInterval": 5,
"merkleRefreshInterval": 60,
"daemon": { "daemon": {
"host": "localhost", "host": "localhost",
"port": 19334, "port": 19334,
"user": "testnet", "user": "testnet",
"password": "testnet1" "password": "testnet1"
} }
} }

View File

@ -1,40 +0,0 @@
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.client,
data.blockHeaderHex,
data.jobId,
data.extraNonce1,
data.extraNonce2,
data.nTime,
data.nonce);
} else {
handleInvalidShare(
data.client,
data.error[0],
data.error[1]);
}
});
function handleValidShare(client, headerHex, jobId, extraNonce1, extraNonce2, nTime, nonce) {
console.log("A new Valid share from "+client.workerName+" has arrived! - "+headerHex);
}
function handleInvalidShare(client, errorCode, errorDescription) {
console.log("Invalid share form "+client.workerName+" ErrorCode: "+errorCode+ " ErrorDescription: "+errorDescription);
}
};
ShareManager.prototype.__proto__ = events.EventEmitter.prototype;

264
index.js
View File

@ -1,233 +1,67 @@
var net = require('net'); var net = require('net');
var events = require('events'); var fs = require('fs');
var fs = require('fs'); var path = require('path');
var async = require('async'); var pool = require('lib/pool.js');
var daemon = require('./libs/daemon.js');
var stratum = require('./libs/stratum.js');
var jobManager = require('./libs/jobManager.js');
var util = require('./libs/util.js');
/**
* Main pool object. It emits the following events:
* - 'started'() - when the pool is effectively started. var index = module.exports = function index(options){
* - 'share'(isValid, dataObj) - In case it's valid the dataObj variable will contain (TODO) and in case it's invalid (TODO)
*/
var pool = module.exports = function pool(coin, authorizeFn){
var _this = this; var _this = this;
var publicKeyBuffer; this.pools = [];
(function Init(){ var emitLog = function(text){
SetupJobManager(); _this.emit('log', text);
SetupDaemonInterface(); };
SetupShareManager();
})();
if (options.blockNotifyListener.enabled){
function SetupJobManager(){ SetupBlockListener();
_this.jobManager = new jobManager({
algorithm : coin.options.algorithm,
address : coin.options.address
});
_this.jobManager.on('newBlock', function(blockTemplate){
if ( typeof(_this.stratumServer ) === 'undefined') {
console.warn("Stratum server still not started! cannot broadcast block!");
} else {
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
}
}).on('blockFound', function(blockHex, headerHex, third){
if (coin.options.hasSubmitMethod) {
_this.daemon.cmd('submitblock',
[blockHex],
function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblock", JSON.stringify(error), JSON.stringify(result));
}
);
} else {
_this.daemon.cmd('getblocktemplate',
[{'mode': 'submit', 'data': blockHex}],
function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result));
}
);
}
});
} }
function SetupDaemonInterface(){ function SetupBlockListener(){
console.log('Connecting to daemon for ' + coin.options.name); console.log("Block listener is enabled, starting server on port " + config.blockNotifyListener.port);
_this.daemon = new daemon.interface(coin.options.daemon); var blockNotifyServer = net.createServer(function(c) {
_this.daemon.on('online', function(){ emitLog('Block listener has incoming connection');
async.parallel({ var data = '';
addressInfo: function(callback){ c.on('data', function(d){
_this.daemon.cmd('validateaddress', emitLog('Block listener received blocknotify data');
[coin.options.address], data += d;
function(error, result){ if (data.slice(-1) === '\n'){
if (error){ c.end();
console.log('validateaddress rpc error for ' + coin.options.name); }
callback(error); });
} else if (!result.isvalid) { c.on('end', function() {
console.log('address is not valid for ' + coin.options.name);
callback("address-not-valid"); emitLog('Block listener connection ended');
} else {
console.log("LOALASD"+JSON.stringify(result)) var message = JSON.parse(data);
callback(error, result); if (message.password === config.blockNotifyListener.password){
}
for (var i = 0; i < this.pools.length; i++){
if (this.pools[i].options.symbol === message.coin){
this.pools[i].processBlockNotify(message.blockHash)
return;
} }
); }
}, emitLog('Block listener could not find pool to notify');
submitMethod: function(callback){
_this.daemon.cmd('submitblock',
[],
function(error, result){
if (error && error.message === 'Method not found')
callback(null, false);
else
callback(null, true);
}
);
} }
}, function(err, results){ else
if (err) return; emitLog('Block listener received notification with incorrect password');
console.log('Connected to daemon for ' + coin.options.name);
coin.options.hasSubmitMethod = results.submitMethod;
publicKeyBuffer = coin.options.reward === 'POW' ?
util.script_to_address(results.addressInfo.address) :
util.script_to_pubkey(results.addressInfo.pubkey);
StartStratumServer();
SetupBlockPolling();
}); });
});
}).on('startFailed', function(){ blockNotifyServer.listen(options.blockNotifyListener.port, function() {
console.log('Failed to start daemon for ' + coin.name); emitLog('Block notify listener server started on port ' + options.blockNotifyListener.port)
}); });
} }
function StartStratumServer(){ this.createPool = function(poolOptions, authorizeFn){
console.log('Stratum server starting on port ' + coin.options.stratumPort + ' for ' + coin.options.name); var newPool = new pool(poolOptions, authorizeFn);
_this.stratumServer = new stratum.Server({ this.pools.push(newPool);
port : coin.options.stratumPort, return newPool;
authorizeFn : authorizeFn, };
});
_this.stratumServer.on('started', function(){
console.log('Stratum server started on port ' + coin.options.stratumPort + ' for ' + coin.options.name);
}).on('client.connected', function(client){
client.on('subscription', function(params, resultCallback){
var extraNonce = _this.jobManager.extraNonceCounter.next();
var extraNonce2Size = _this.jobManager.extraNonce2Size;
resultCallback(null,
extraNonce,
extraNonce2Size
);
var clientThis = this;
//if (clientThis.authorized) {
if (typeof(_this.jobManager.currentJob) !== 'undefined') {
clientThis.sendMiningJob(_this.jobManager.currentJob.getJobParams());
}
//}
}).on('submit', function(params, resultCallback){
var result =_this.jobManager.processShare(
params.jobId,
client.difficulty,
client.extraNonce1,
params.extraNonce2,
params.nTime,
params.nonce
);
if (result.error){
resultCallback(result.error);
_this.emit('share', false, {
client : client,
error : result.error
});
} else {
resultCallback(null, true);
_this.emit('share', true, {
client : client,
blockHeaderHex : result.headerHEX,
workerName : params.name,
jobId : params.jobId,
extraNonce2 : params.extraNonce2,
nTime : params.nTime,
nonce : params.nonce
});
}
});
});
}
function SetupShareManager(){
this.shareManager = undefined; // just for us to know that the variable should be this one.
}
function SetupBlockPolling(){
if (coin.options.blockRefreshInterval === 0){
console.log('Block template polling has been disabled for ' + coin.options.name);
return;
}
var pollingInterval = coin.options.blockRefreshInterval * 1000;
var pollTimeout;
var setPoll;
setInterval(function () {
GetBlockTemplate(function(error, result) {
if (error)
console.error("Block polling error getting block template for " + coin.options.name);
});
}, pollingInterval);
console.log('Block polling setup for every ' + pollingInterval + ' milliseconds for ' + coin.options.name);
}
function GetBlockTemplate(callback){
_this.daemon.cmd('getblocktemplate',
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
function(error, result){
if (error) {
callback(error);
} else {
_this.jobManager.processTemplate(result, publicKeyBuffer);
callback(null, result);
}
}
);
}
/**
* 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){
if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
GetBlockTemplate(function(error, result){
if (error)
console.error('Block notify error getting block template for ' + coin.options.name);
})
}
}
}; };
pool.prototype.__proto__ = events.EventEmitter.prototype; index.prototype.__proto__ = events.EventEmitter.prototype;

View File

@ -1,7 +1,7 @@
var binpack = require('binpack'); var binpack = require('binpack');
var merkleTree = require('./merkleTree.js'); var merkleTree = require('./merkleTree.js');
var transactions = require('./transactions.js'); var transactions = require('./transactions.js');
var util = require('./util.js'); var util = require('./util.js');
/** /**

View File

@ -1,6 +1,6 @@
var http = require('http'); var http = require('http');
var cp = require('child_process'); var cp = require('child_process');
var events = require('events'); var events = require('events');
var startFailedTimeout = 120; //seconds var startFailedTimeout = 120; //seconds
/** /**

View File

@ -193,14 +193,12 @@ var JobManager = module.exports = function JobManager(options){
var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer); var blockBuf = job.serializeBlock(headerBuffer, coinbaseBuffer);
console.log("EXPECTED BLOCK HASH: "+blockHashHex(headerBuffer)); // NOT WORKING :(? 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)){ // TODO: this seems to not be working properly
return {error: [23, 'low difficulty share', null]}; 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)};

226
lib/pool.js Normal file
View File

@ -0,0 +1,226 @@
var net = require('net');
var events = require('events');
var fs = require('fs');
var async = require('async');
var daemon = require('./daemon.js');
var stratum = require('./stratum.js');
var jobManager = require('./jobManager.js');
var util = require('./util.js');
/**
* Main pool object. It emits the following events:
* - 'started'() - when the pool is effectively started.
* - 'share'(isValid, dataObj) - In case it's valid the dataObj variable will contain (TODO) and in case it's invalid (TODO)
*/
var pool = module.exports = function pool(options, authorizeFn){
this.options = options;
var _this = this;
var publicKeyBuffer;
var emitLog = function(text){ _this.emit('log', text) };
(function Init(){
SetupJobManager();
SetupDaemonInterface();
})();
function SetupJobManager(){
_this.jobManager = new jobManager({
algorithm : options.algorithm,
address : options.address
});
_this.jobManager.on('newBlock', function(blockTemplate){
if ( typeof(_this.stratumServer ) === 'undefined') {
console.warn("Stratum server still not started! cannot broadcast block!");
} else {
_this.stratumServer.broadcastMiningJobs(blockTemplate.getJobParams());
}
}).on('blockFound', function(blockHex, headerHex, third){
if (options.hasSubmitMethod) {
_this.daemon.cmd('submitblock',
[blockHex],
function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblock", JSON.stringify(error), JSON.stringify(result));
}
);
} else {
_this.daemon.cmd('getblocktemplate',
[{'mode': 'submit', 'data': blockHex}],
function(error, result){
console.log(JSON.stringify(error));
console.log(JSON.stringify(result));
console.log("submitblockgetBlockTEmplate", JSON.stringify(error), JSON.stringify(result));
}
);
}
});
}
function SetupDaemonInterface(){
emitLog('Connecting to daemon');
_this.daemon = new daemon.interface(options.daemon);
_this.daemon.on('online', function(){
async.parallel({
addressInfo: function(callback){
_this.daemon.cmd('validateaddress',
[options.address],
function(error, result){
if (error){
emitLog('validateaddress rpc error');
callback(error);
} else if (!result.isvalid) {
emitLog('address is not valid');
callback("address-not-valid");
} else {
callback(error, result);
}
}
);
},
submitMethod: function(callback){
_this.daemon.cmd('submitblock',
[],
function(error, result){
if (error && error.message === 'Method not found')
callback(null, false);
else
callback(null, true);
}
);
}
}, function(err, results){
if (err) return;
emitLog('Connected to daemon');
options.hasSubmitMethod = results.submitMethod;
publicKeyBuffer = options.reward === 'POW' ?
util.script_to_address(results.addressInfo.address) :
util.script_to_pubkey(results.addressInfo.pubkey);
StartStratumServer();
SetupBlockPolling();
});
}).on('startFailed', function(){
emitLog('Failed to start daemon');
});
}
function StartStratumServer(){
emitLog('Stratum server starting on port ' + options.stratumPort);
_this.stratumServer = new stratum.Server({
port: options.stratumPort,
authorizeFn: authorizeFn
});
_this.stratumServer.on('started', function(){
emitLog('Stratum server started on port ' + options.stratumPort);
}).on('client.connected', function(client){
client.on('subscription', function(params, resultCallback){
var extraNonce = _this.jobManager.extraNonceCounter.next();
var extraNonce2Size = _this.jobManager.extraNonce2Size;
resultCallback(null,
extraNonce,
extraNonce2Size
);
this.sendAndSetDifficultyIfNew(options.difficulty);
this.sendMiningJob(_this.jobManager.currentJob.getJobParams());
}).on('submit', function(params, resultCallback){
var result =_this.jobManager.processShare(
params.jobId,
client.difficulty,
client.extraNonce1,
params.extraNonce2,
params.nTime,
params.nonce
);
if (result.error){
resultCallback(result.error);
_this.emit('share', false, {
client : client,
error : result.error
});
} else {
resultCallback(null, true);
_this.emit('share', true, {
client : client,
blockHeaderHex : result.headerHEX,
workerName : params.name,
jobId : params.jobId,
extraNonce2 : params.extraNonce2,
nTime : params.nTime,
nonce : params.nonce
});
}
});
});
}
function SetupBlockPolling(){
if (options.blockRefreshInterval === 0){
emitLog('Block template polling has been disabled');
return;
}
var pollingInterval = options.blockRefreshInterval * 1000;
var pollTimeout;
var setPoll;
setInterval(function () {
GetBlockTemplate(function(error, result) {
if (error)
console.error("Block polling error getting block template for " + options.name);
});
}, pollingInterval);
emitLog('Block polling setup for every ' + pollingInterval + ' milliseconds');
}
function GetBlockTemplate(callback){
_this.daemon.cmd('getblocktemplate',
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
function(error, result){
if (error) {
callback(error);
} else {
_this.jobManager.processTemplate(result, publicKeyBuffer);
callback(null, result);
}
}
);
}
/**
* 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){
if (blockHash !== _this.jobManager.currentJob.rpcData.previousblockhash){
GetBlockTemplate(function(error, result){
if (error)
console.error('Block notify error getting block template for ' + options.name);
})
}
}
};
pool.prototype.__proto__ = events.EventEmitter.prototype;

View File

@ -92,41 +92,18 @@ var StratumClient = function(options){
_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(result) {
_this.authorized = ( ! err && authorized ); _this.authorized = (!result.error && result.authorized);
sendJson({ sendJson({
id : message.id, id : message.id,
result : _this.authorized, result : _this.authorized,
error : err error : result.error
}); });
// If the authorizer wants us to close the socket lets do it. // If the authorizer wants us to close the socket lets do it.
if (typeof(shouldCloseSocket) === 'boolean' && shouldCloseSocket) { if (result.disconnect === true) {
options.socket.end(); options.socket.end();
} }
// Send difficulty
if (typeof(difficulty) === 'function') {
difficulty(_this.workerName, function(err, diff) {
if (err) {
console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err));
options.socket.end();
} else {
_this.sendAndSetDifficultyIfNew(diff);
}
});
} else if (typeof(difficulty) === 'number') {
_this.sendAndSetDifficultyIfNew(difficulty);
} else {
process.exit("Difficulty from authorizeFn callback is neither a function or a number");
}
if (_this.requestedSubscriptionBeforeAuth === true) {
delete _this.requestedSubscriptionBeforeAuth; // we do not need this anymore.
//TODO: We should send a mining job here. For now we send it before the auth has taken place but.....
}
}); });
} }

2
node_modules/bignum/package.json generated vendored
View File

@ -2,7 +2,7 @@
"name": "bignum", "name": "bignum",
"version": "0.6.2", "version": "0.6.2",
"description": "Arbitrary-precision integer arithmetic using OpenSSL", "description": "Arbitrary-precision integer arithmetic using OpenSSL",
"main": "./index.js", "main": "./pool.js",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://github.com/justmoon/node-bignum.git" "url": "http://github.com/justmoon/node-bignum.git"

View File

@ -131,7 +131,7 @@ exports.getFileName = function getFileName () {
* somewhere in the module tree. The "root directory" is the directory * somewhere in the module tree. The "root directory" is the directory
* containing the `package.json` file. * containing the `package.json` file.
* *
* In: /home/nate/node-native-module/lib/index.js * In: /home/nate/node-native-module/lib/pool.js
* Out: /home/nate/node-native-module * Out: /home/nate/node-native-module
*/ */

View File

@ -131,7 +131,7 @@ exports.getFileName = function getFileName () {
* somewhere in the module tree. The "root directory" is the directory * somewhere in the module tree. The "root directory" is the directory
* containing the `package.json` file. * containing the `package.json` file.
* *
* In: /home/nate/node-native-module/lib/index.js * In: /home/nate/node-native-module/lib/pool.js
* Out: /home/nate/node-native-module * Out: /home/nate/node-native-module
*/ */

View File

@ -131,7 +131,7 @@ exports.getFileName = function getFileName () {
* somewhere in the module tree. The "root directory" is the directory * somewhere in the module tree. The "root directory" is the directory
* containing the `package.json` file. * containing the `package.json` file.
* *
* In: /home/nate/node-native-module/lib/index.js * In: /home/nate/node-native-module/lib/pool.js
* Out: /home/nate/node-native-module * Out: /home/nate/node-native-module
*/ */

View File

@ -131,7 +131,7 @@ exports.getFileName = function getFileName () {
* somewhere in the module tree. The "root directory" is the directory * somewhere in the module tree. The "root directory" is the directory
* containing the `package.json` file. * containing the `package.json` file.
* *
* In: /home/nate/node-native-module/lib/index.js * In: /home/nate/node-native-module/lib/pool.js
* Out: /home/nate/node-native-module * Out: /home/nate/node-native-module
*/ */

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "http-server",
"version": "0.0.1",
"author": "Matthew Little",
"description": "High performance Stratum poolserver in Node.js",
"contributors": [
"vekexasia"
],
"bin": {
"block-notify": "./scripts/blockNotify.js"
},
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/zone117x/node-stratum.git"
},
"keywords": [
"stratum",
"pool",
"server",
"poolserver",
"bitcoin",
"litecoin",
"scrypt"
],
"bundledDependencies": [
"scrypt256-hash",
"scrypt-jane-hash",
"quark-hash",
"binpack",
"bignum",
"buffertools",
"base58-native"
],
"license": "GPL-2.0",
"engines": {
"node": ">=0.10"
}
}