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,114 +1,114 @@
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');
/** /**
* The BlockTemplate class holds a single job. * The BlockTemplate class holds a single job.
* and provides serveral methods to validate and submit it to the daemon coin * and provides serveral methods to validate and submit it to the daemon coin
**/ **/
var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){ var BlockTemplate = module.exports = function BlockTemplate(rpcData, publicKey, extraNoncePlaceholder){
//private members //private members
var submits = []; var submits = [];
function getMerkleHashes(steps){ function getMerkleHashes(steps){
return steps.map(function(step){ return steps.map(function(step){
return step.toString('hex'); return step.toString('hex');
}); });
} }
function getTransactionBuffers(txs){ function getTransactionBuffers(txs){
var txHashes = txs.map(function(tx){ var txHashes = txs.map(function(tx){
return util.uint256BufferFromHash(tx.hash); return util.uint256BufferFromHash(tx.hash);
}); });
return [null].concat(txHashes); return [null].concat(txHashes);
} }
//public members //public members
this.rpcData = rpcData; this.rpcData = rpcData;
this.jobId = null; 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){
return new Buffer(tx.data, 'hex'); return new Buffer(tx.data, 'hex');
})); }));
this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions)); this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions));
this.merkleBranch = getMerkleHashes(this.merkleTree.steps); this.merkleBranch = getMerkleHashes(this.merkleTree.steps);
this.generationTransaction = new transactions.Generation( this.generationTransaction = new transactions.Generation(
rpcData, rpcData,
publicKey, publicKey,
extraNoncePlaceholder extraNoncePlaceholder
); );
this.setJobId = function (jobId) { this.setJobId = function (jobId) {
this.jobId = 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],
extraNonce1, extraNonce1,
extraNonce2, extraNonce2,
this.generationTransaction.coinbase[1] this.generationTransaction.coinbase[1]
]); ]);
}; };
this.serializeHeader = function(merkleRoot, nTime, nonce){ this.serializeHeader = function(merkleRoot, nTime, nonce){
var header = new Buffer(80); var header = new Buffer(80);
var position = 0; var position = 0;
header.write(nonce, position, 4, 'hex'); header.write(nonce, position, 4, 'hex');
header.write(rpcData.bits, position += 4, 4, 'hex'); header.write(rpcData.bits, position += 4, 4, 'hex');
header.write(nTime, position += 4, 4, 'hex'); header.write(nTime, position += 4, 4, 'hex');
header.write(merkleRoot, position += 4, 32, 'hex'); header.write(merkleRoot, position += 4, 32, 'hex');
header.write(rpcData.previousblockhash, position += 32, 32, 'hex'); header.write(rpcData.previousblockhash, position += 32, 32, 'hex');
header.writeUInt32BE(rpcData.version, position + 32); header.writeUInt32BE(rpcData.version, position + 32);
var header = util.reverseBuffer(header); var header = util.reverseBuffer(header);
return header; return header;
}; };
this.serializeBlock = function(header, coinbase){ this.serializeBlock = function(header, coinbase){
return Buffer.concat([ return Buffer.concat([
header, header,
util.varIntBuffer(this.rpcData.transactions.length + 1), util.varIntBuffer(this.rpcData.transactions.length + 1),
coinbase, coinbase,
this.transactionData this.transactionData
]); ]);
}; };
this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){ this.registerSubmit = function(extraNonce1, extraNonce2, nTime, nonce){
var submission = extraNonce1 + extraNonce2 + nTime + nonce; var submission = extraNonce1 + extraNonce2 + nTime + nonce;
if (submits.indexOf(submission) === -1){ if (submits.indexOf(submission) === -1){
submits.push(submission); submits.push(submission);
return true; return true;
} }
return false; return false;
}; };
this.getJobParams = function(){ this.getJobParams = function(){
if (!this.jobParams){ if (!this.jobParams){
this.jobParams = [ this.jobParams = [
this.jobId, this.jobId,
this.prevHashReversed, this.prevHashReversed,
this.generationTransaction.coinbase[0].toString('hex'), this.generationTransaction.coinbase[0].toString('hex'),
this.generationTransaction.coinbase[1].toString('hex'), this.generationTransaction.coinbase[1].toString('hex'),
this.merkleBranch, this.merkleBranch,
binpack.packInt32(this.rpcData.version, 'big').toString('hex'), binpack.packInt32(this.rpcData.version, 'big').toString('hex'),
this.rpcData.bits, this.rpcData.bits,
binpack.packUInt32(this.rpcData.curtime, 'big').toString('hex'), binpack.packUInt32(this.rpcData.curtime, 'big').toString('hex'),
true true
]; ];
} }
return this.jobParams; return this.jobParams;
} }
//this.jobParams = this.getJobParams(); //this.jobParams = this.getJobParams();
//console.log(JSON.stringify(this.jobParams, null, ' ').replace(/\n/g ,'')); //console.log(JSON.stringify(this.jobParams, null, ' ').replace(/\n/g ,''));
} }

View File

@ -1,121 +1,121 @@
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
/** /**
* The daemon interface interacts with the coin daemon by using the rpc interface. * The daemon interface interacts with the coin daemon by using the rpc interface.
* in otder to make it work it needs, as constructor, an object containing * in otder to make it work it needs, as constructor, an object containing
* - 'host' : hostname where the coin lives * - 'host' : hostname where the coin lives
* - 'port' : port where the coin accepts rpc connections * - 'port' : port where the coin accepts rpc connections
* - 'user' : username of the coin for the rpc interface * - 'user' : username of the coin for the rpc interface
* - 'password': password for the rpc interface of the coin * - 'password': password for the rpc interface of the coin
**/ **/
function DaemonInterface(options){ function DaemonInterface(options){
//private members //private members
var _this = this; var _this = this;
this.options = options; this.options = options;
(function init(){ (function init(){
isOnline(function(online){ isOnline(function(online){
if (online) if (online)
_this.emit('online'); _this.emit('online');
else if (options.startIfOffline){ else if (options.startIfOffline){
me.start(); me.start();
emitOnline(); emitOnline();
} }
}); });
})(); })();
function emitOnline(){ function emitOnline(){
var startedTime = Date.now(); var startedTime = Date.now();
var checkFunc = function(){ var checkFunc = function(){
isOnline(function(online){ isOnline(function(online){
if (online) if (online)
_this.emit('online'); _this.emit('online');
else if (Date.now() - startedTime < startFailedTimeout * 1000) else if (Date.now() - startedTime < startFailedTimeout * 1000)
setTimeout(checkFunc, 2000); setTimeout(checkFunc, 2000);
else else
_this.emit('startFailed'); _this.emit('startFailed');
}); });
}; };
checkFunc(); checkFunc();
} }
function isOnline(callback){ function isOnline(callback){
cmd('getinfo', [], function(error, result){ cmd('getinfo', [], function(error, result){
if (error) if (error)
callback(false); callback(false);
else else
callback(true); callback(true);
}); });
} }
function cmd(method, params, callback){ function cmd(method, params, callback){
var requestJson = JSON.stringify({ var requestJson = JSON.stringify({
id: Date.now() + Math.floor(Math.random() * 10), id: Date.now() + Math.floor(Math.random() * 10),
method: method, method: method,
params: params params: params
}); });
if (method == 'submitblock') { if (method == 'submitblock') {
console.log("SUBMITBLOCK daemon"); console.log("SUBMITBLOCK daemon");
console.log(requestJson); console.log(requestJson);
} }
var options = { var options = {
hostname: (typeof(_this.options.host) === 'undefined'?'localhost':_this.options.host), hostname: (typeof(_this.options.host) === 'undefined'?'localhost':_this.options.host),
port : _this.options.port, port : _this.options.port,
method : 'POST', method : 'POST',
auth : _this.options.user + ':' + _this.options.password, auth : _this.options.user + ':' + _this.options.password,
headers : { headers : {
'Content-Length': requestJson.length 'Content-Length': requestJson.length
} }
}; };
var req = http.request(options, function(res) { var req = http.request(options, function(res) {
var data = ''; var data = '';
res.setEncoding('utf8'); res.setEncoding('utf8');
res.on('data', function (chunk) { res.on('data', function (chunk) {
data += chunk; data += chunk;
}); });
res.on('end', function(){ res.on('end', function(){
var dataJson = JSON.parse(data); var dataJson = JSON.parse(data);
callback(dataJson.error, dataJson.result); callback(dataJson.error, dataJson.result);
}); });
}); });
req.on('error', function(e) { req.on('error', function(e) {
if (e.code === 'ECONNREFUSED') if (e.code === 'ECONNREFUSED')
callback({type: 'offline', message: e.message}); callback({type: 'offline', message: e.message});
else else
callback({type: 'request error', message: e.message}); callback({type: 'request error', message: e.message});
}); });
req.end(requestJson); req.end(requestJson);
} }
//public members //public members
this.isOnline = isOnline; this.isOnline = isOnline;
this.cmd = cmd; this.cmd = cmd;
this.start = function(){ this.start = function(){
var cmdArgs = [ var cmdArgs = [
'-rpcport=' + _this.options.port, '-rpcport=' + _this.options.port,
'-rpcuser=' + _this.options.user, '-rpcuser=' + _this.options.user,
'-rpcpassword=' + _this.options.password, '-rpcpassword=' + _this.options.password,
'-blocknotify=' + _this.options.blocknotify '-blocknotify=' + _this.options.blocknotify
]; ];
var child = cp.spawn(_this.options.bin, cmdArgs, { detached: true, stdio: [ 'ignore', 'ignore', 'ignore' ] }); var child = cp.spawn(_this.options.bin, cmdArgs, { detached: true, stdio: [ 'ignore', 'ignore', 'ignore' ] });
child.unref(); child.unref();
console.log('started daemon'); console.log('started daemon');
}; };
} }
DaemonInterface.prototype.__proto__ = events.EventEmitter.prototype; DaemonInterface.prototype.__proto__ = events.EventEmitter.prototype;
exports.interface = DaemonInterface; exports.interface = DaemonInterface;

View File

@ -1,209 +1,207 @@
var events = require('events'); var events = require('events');
var binpack = require('binpack'); var binpack = require('binpack');
var bignum = require('bignum'); var bignum = require('bignum');
var scrypt = require('scrypt256-hash'); var scrypt = require('scrypt256-hash');
var quark = require('quark-hash'); var quark = require('quark-hash');
var scryptJane = require('scrypt-jane-hash') var scryptJane = require('scrypt-jane-hash')
var util = require('./util.js'); var util = require('./util.js');
var blockTemplate = require('./blockTemplate.js'); var blockTemplate = require('./blockTemplate.js');
//Unique extranonce per subscriber //Unique extranonce per subscriber
var ExtraNonceCounter = function(){ var ExtraNonceCounter = function(){
var instanceId = 31; var instanceId = 31;
var counter = instanceId << 27; var counter = instanceId << 27;
var size = binpack.packUInt32(counter, 'big').length; var size = binpack.packUInt32(counter, 'big').length;
this.next = function(){ this.next = function(){
var extraNonce = binpack.packUInt32(counter++, 'big'); var extraNonce = binpack.packUInt32(counter++, 'big');
return extraNonce.toString('hex'); return extraNonce.toString('hex');
}; };
this.size = function(){ this.size = function(){
return size; return size;
}; };
}; };
//Unique job per new block template //Unique job per new block template
var JobCounter = function(){ var JobCounter = function(){
var counter = 0; var counter = 0;
this.next = function(){ this.next = function(){
counter++; counter++;
if (counter % 0xffff === 0) if (counter % 0xffff === 0)
counter = 1; counter = 1;
return this.cur(); return this.cur();
}; };
this.cur = function () { this.cur = function () {
return counter.toString(16); return counter.toString(16);
}; };
}; };
/** /**
* Emits: * Emits:
* - 'newBlock'(blockTemplate) - when a new block (previously unknown to the JobManager) is being added * - 'newBlock'(blockTemplate) - when a new block (previously unknown to the JobManager) is being added
* - 'blockFound'(serializedBlock) - when a worker finds a block. * - 'blockFound'(serializedBlock) - when a worker finds a block.
**/ **/
var JobManager = module.exports = function JobManager(options){ var JobManager = module.exports = function JobManager(options){
//private members //private members
var _this = this; var _this = this;
var jobCounter = new JobCounter(); var jobCounter = new JobCounter();
var jobs = {}; var jobs = {};
/** /**
* It only checks if the blockTemplate is already in our jobs list. * It only checks if the blockTemplate is already in our jobs list.
* @returns true if it's a new block, false otherwise. * @returns true if it's a new block, false otherwise.
* used by onNewTemplate * used by onNewTemplate
**/ **/
function CheckNewIfNewBlock(prevBlockHash){ function CheckNewIfNewBlock(prevBlockHash){
var newBlock = true; var newBlock = true;
for(var job in jobs){ for(var job in jobs){
if (jobs[job].rpcData.previousblockhash === prevBlockHash) { if (jobs[job].rpcData.previousblockhash === prevBlockHash) {
newBlock = false; newBlock = false;
} }
} }
return newBlock; return newBlock;
} }
var diffDividend = (function(){ var diffDividend = (function(){
switch(options.algorithm){ switch(options.algorithm){
case 'sha256': case 'sha256':
return 0x00000000ffff0000000000000000000000000000000000000000000000000000; return 0x00000000ffff0000000000000000000000000000000000000000000000000000;
case 'scrypt': case 'scrypt':
case 'scrypt-jane': case 'scrypt-jane':
return 0x0000ffff00000000000000000000000000000000000000000000000000000000; return 0x0000ffff00000000000000000000000000000000000000000000000000000000;
case 'quark': case 'quark':
return 0x000000ffff000000000000000000000000000000000000000000000000000000; return 0x000000ffff000000000000000000000000000000000000000000000000000000;
} }
})(); })();
var hashDigest = (function(){ var hashDigest = (function(){
switch(options.algorithm){ switch(options.algorithm){
case 'sha256': case 'sha256':
return function(){ return function(){
return util.doublesha.apply(this, arguments); return util.doublesha.apply(this, arguments);
} }
case 'scrypt': case 'scrypt':
return function(){ return function(){
return scrypt.digest.apply(this, arguments); return scrypt.digest.apply(this, arguments);
} }
case 'scrypt-jane': case 'scrypt-jane':
return function(){ return function(){
return scryptJane.digest.apply(this, arguments); return scryptJane.digest.apply(this, arguments);
} }
case 'quark': case 'quark':
return function(){ return function(){
return quark.digest.apply(this, arguments); return quark.digest.apply(this, arguments);
} }
} }
})(); })();
/** /**
* Tries to estimate the resulting block hash * Tries to estimate the resulting block hash
* This is only valid for scrypt apparently. * This is only valid for scrypt apparently.
* @author vekexasia * @author vekexasia
**/ **/
function blockHashHex(headerBuffer) { function blockHashHex(headerBuffer) {
var result = new Buffer(80); var result = new Buffer(80);
for (var i=0; i<20; i++) { for (var i=0; i<20; i++) {
for (var j=0; j<4; j++) { for (var j=0; j<4; j++) {
result[i*4+j] = headerBuffer[i*4+3-j]; result[i*4+j] = headerBuffer[i*4+3-j];
} }
} }
var shaed = util.reverseBuffer(util.doublesha(result)); var shaed = util.reverseBuffer(util.doublesha(result));
return shaed.toString('hex'); // return the expected block hash return shaed.toString('hex'); // return the expected block hash
} }
//public members //public members
this.extraNonceCounter = new ExtraNonceCounter(); this.extraNonceCounter = new ExtraNonceCounter();
this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex'); this.extraNoncePlaceholder = new Buffer('f000000ff111111f', 'hex');
this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size(); this.extraNonce2Size = this.extraNoncePlaceholder.length - this.extraNonceCounter.size();
this.currentJob; this.currentJob;
this.processTemplate = function(rpcData, publicKey){ this.processTemplate = function(rpcData, publicKey){
if (CheckNewIfNewBlock(rpcData.previousblockhash)){ if (CheckNewIfNewBlock(rpcData.previousblockhash)){
var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder); var tmpBlockTemplate = new blockTemplate(rpcData, publicKey, _this.extraNoncePlaceholder);
tmpBlockTemplate.setJobId(jobCounter.next()); tmpBlockTemplate.setJobId(jobCounter.next());
jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate; jobs[tmpBlockTemplate.jobId] = tmpBlockTemplate;
this.currentJob = jobs[tmpBlockTemplate.jobId]; this.currentJob = jobs[tmpBlockTemplate.jobId];
_this.emit('newBlock', tmpBlockTemplate); _this.emit('newBlock', tmpBlockTemplate);
} }
}; };
this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){ this.processShare = function(jobId, difficulty, extraNonce1, extraNonce2, nTime, nonce){
var submitTime = Date.now() / 1000 | 0; var submitTime = Date.now() / 1000 | 0;
if (extraNonce2.length / 2 !== _this.extraNonce2Size) if (extraNonce2.length / 2 !== _this.extraNonce2Size)
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});
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 :(? 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 // TODO: this seems to not be working properly
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, headerHEX: headerBigNum.toString(16)};
};
return {result: true, headerHEX: headerBigNum.toString(16)}; };
};
};
JobManager.prototype.__proto__ = events.EventEmitter.prototype; JobManager.prototype.__proto__ = events.EventEmitter.prototype;

View File

@ -1,67 +1,67 @@
/* /*
Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py Ported from https://github.com/slush0/stratum-mining/blob/master/lib/merkletree.py
*/ */
var util = require('./util.js'); var util = require('./util.js');
var MerkleTree = module.exports = function MerkleTree(data){ var MerkleTree = module.exports = function MerkleTree(data){
function merkleJoin(h1, h2){ function merkleJoin(h1, h2){
var joined = Buffer.concat([h1, h2]); var joined = Buffer.concat([h1, h2]);
var dhashed = util.doublesha(joined); var dhashed = util.doublesha(joined);
return dhashed; return dhashed;
} }
function calculateSteps(data){ function calculateSteps(data){
var L = data; var L = data;
var steps = []; var steps = [];
var PreL = [null]; var PreL = [null];
var StartL = 2; var StartL = 2;
var Ll = L.length; var Ll = L.length;
if (Ll > 1){ if (Ll > 1){
while (true){ while (true){
if (Ll === 1) if (Ll === 1)
break; break;
steps.push(L[1]); steps.push(L[1]);
if (Ll % 2) if (Ll % 2)
L.push(L[L.length - 1]); L.push(L[L.length - 1]);
var Ld = []; var Ld = [];
var r = util.range(StartL, Ll, 2); var r = util.range(StartL, Ll, 2);
r.forEach(function(i){ r.forEach(function(i){
Ld.push(merkleJoin(L[i], L[i + 1])); Ld.push(merkleJoin(L[i], L[i + 1]));
}); });
L = PreL.concat(Ld); L = PreL.concat(Ld);
Ll = L.length; Ll = L.length;
} }
} }
return steps; return steps;
} }
this.data = data; this.data = data;
this.steps = calculateSteps(data); this.steps = calculateSteps(data);
} }
MerkleTree.prototype = { MerkleTree.prototype = {
hashSteps: function(){ hashSteps: function(){
if (!this.stepsHash) if (!this.stepsHash)
this.stepsHash = util.doublesha(Buffer.concat(this.steps)); this.stepsHash = util.doublesha(Buffer.concat(this.steps));
return this.stepsHash; return this.stepsHash;
}, },
withFirst: function(f){ withFirst: function(f){
this.steps.forEach(function(s){ this.steps.forEach(function(s){
f = util.doublesha(Buffer.concat([f, s])); f = util.doublesha(Buffer.concat([f, s]));
}); });
return f; return f;
}, },
merkleRoot: function(){ merkleRoot: function(){
return this.withFirst(this.data[0]); return this.withFirst(this.data[0]);
} }
}; };

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

@ -1,301 +1,278 @@
var net = require('net'); var net = require('net');
var events = require('events'); var events = require('events');
var binpack = require('binpack'); var binpack = require('binpack');
var util = require('./util.js'); var util = require('./util.js');
var SubscriptionCounter = function(){ var SubscriptionCounter = function(){
var count = 0; var count = 0;
var padding = 'deadbeefcafebabe'; var padding = 'deadbeefcafebabe';
return { return {
next: function(){ next: function(){
count++; count++;
if (Number.MAX_VALUE === count) count = 0; if (Number.MAX_VALUE === count) count = 0;
return padding + binpack.packUInt64(count, 'big').toString('hex'); return padding + binpack.packUInt64(count, 'big').toString('hex');
} }
}; };
}; };
/** /**
* Defining each client that connects to the stratum server. * Defining each client that connects to the stratum server.
* Emits: * Emits:
* - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size)) * - 'subscription'(obj, cback(error, extraNonce1, extraNonce2Size))
* - 'submit' FIX THIS. * - 'submit' FIX THIS.
**/ **/
var StratumClient = function(options){ var StratumClient = function(options){
//private members //private members
var _this = this; var _this = this;
(function init(){ (function init(){
setupSocket(); setupSocket();
})(); })();
function handleMessage(message){ function handleMessage(message){
switch(message.method){ switch(message.method){
case 'mining.subscribe': case 'mining.subscribe':
handleSubscribe(message); handleSubscribe(message);
break; break;
case 'mining.authorize': case 'mining.authorize':
handleAuthorize(message); handleAuthorize(message);
break; break;
case 'mining.submit': case 'mining.submit':
handleSubmit(message); handleSubmit(message);
break; break;
case 'mining.get_transactions': case 'mining.get_transactions':
sendJson({ sendJson({
id : null, id : null,
result : [], result : [],
error : true error : true
}); });
break; break;
default: default:
console.dir('unknown stratum client message: ' + JSON.stringify(message)); console.dir('unknown stratum client message: ' + JSON.stringify(message));
break; break;
} }
} }
function handleSubscribe(message){ function handleSubscribe(message){
if (! _this._authorized ) { if (! _this._authorized ) {
_this.requestedSubscriptionBeforeAuth = true; _this.requestedSubscriptionBeforeAuth = true;
} }
_this.emit('subscription', _this.emit('subscription',
{}, {},
function(error, extraNonce1, extraNonce2Size){ function(error, extraNonce1, extraNonce2Size){
if (error){ if (error){
sendJson({ sendJson({
id: message.id, id: message.id,
result: null, result: null,
error: error error: error
}); });
return; return;
} }
_this.extraNonce1 = extraNonce1; _this.extraNonce1 = extraNonce1;
sendJson({ sendJson({
id: message.id, id: message.id,
result: [ result: [
["mining.notify", options.subscriptionId], ["mining.notify", options.subscriptionId],
extraNonce1, extraNonce1,
extraNonce2Size extraNonce2Size
], ],
error: null error: null
}); });
} }
); );
} }
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(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) { function handleSubmit(message){
if (err) { if (!_this.authorized){
console.error("Cannot set difficulty for "+_this.workernName+" error: "+JSON.stringify(err)); sendJson({
options.socket.end(); id : message.id,
} else { result: null,
_this.sendAndSetDifficultyIfNew(diff); error : [24, "unauthorized worker", null]
} });
return;
}); }
} else if (typeof(difficulty) === 'number') { if (!_this.extraNonce1){
_this.sendAndSetDifficultyIfNew(difficulty); sendJson({
} else { id : message.id,
process.exit("Difficulty from authorizeFn callback is neither a function or a number"); result: null,
} error : [25, "not subscribed", null]
});
if (_this.requestedSubscriptionBeforeAuth === true) { return;
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..... _this.emit('submit',
} {
name : message.params[0],
}); jobId : message.params[1],
} extraNonce2 : message.params[2],
nTime : message.params[3],
function handleSubmit(message){ nonce : message.params[4]
if (!_this.authorized){ },
sendJson({ function(error, result){
id : message.id, sendJson({
result: null, id : message.id,
error : [24, "unauthorized worker", null] result : result,
}); error : error
return; });
} }
if (!_this.extraNonce1){ );
sendJson({ }
id : message.id,
result: null, function sendJson(){
error : [25, "not subscribed", null] var response = '';
}); for (var i = 0; i < arguments.length; i++){
return; response += JSON.stringify(arguments[i]) + '\n';
} }
_this.emit('submit', options.socket.write(response);
{ }
name : message.params[0],
jobId : message.params[1], function setupSocket(){
extraNonce2 : message.params[2], var socket = options.socket;
nTime : message.params[3], var dataBuffer = '';
nonce : message.params[4] socket.setEncoding('utf8');
}, socket.on('data', function(d){
function(error, result){ console.log('request: ' + d);
sendJson({ dataBuffer += d;
id : message.id, if (dataBuffer.slice(-1) === '\n'){
result : result, var messages = dataBuffer.split('\n');
error : error messages.forEach(function(message){
}); if (message.trim() === '') return;
} var messageJson;
); try{
} messageJson = JSON.parse(message);
}
function sendJson(){ catch(e){
var response = ''; console.log('could not parse stratum client socket message: ' + message);
for (var i = 0; i < arguments.length; i++){ }
response += JSON.stringify(arguments[i]) + '\n'; if (messageJson) {
} handleMessage(messageJson);
options.socket.write(response); }
} });
dataBuffer = '';
function setupSocket(){ }
var socket = options.socket; });
var dataBuffer = ''; socket.on('end', function() {
socket.setEncoding('utf8'); _this.emit('socketDisconnect')
socket.on('data', function(d){ console.log('stratum client disconnected');
console.log('request: ' + d); });
dataBuffer += d; socket.on('error', function(){
if (dataBuffer.slice(-1) === '\n'){ _this.emit('socketError');
var messages = dataBuffer.split('\n'); console.log('stratum client socket error');
messages.forEach(function(message){ });
if (message.trim() === '') return; }
var messageJson;
try{
messageJson = JSON.parse(message); //public members
}
catch(e){ /**
console.log('could not parse stratum client socket message: ' + message); * IF the given difficulty is valid and new it'll send it to the client.
} * returns boolean
if (messageJson) { **/
handleMessage(messageJson); this.sendAndSetDifficultyIfNew = function(difficulty){
} if (typeof(difficulty) != 'number') {
}); console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']');
dataBuffer = ''; return false;
} }
});
socket.on('end', function() { if (difficulty !== this.difficulty) {
_this.emit('socketDisconnect') this.difficulty = difficulty;
console.log('stratum client disconnected'); sendJson({
}); id : null,
socket.on('error', function(){ method: "mining.set_difficulty",
_this.emit('socketError'); params: [difficulty]//[512],
console.log('stratum client socket error'); });
}); return true;
} } else {
return false;
}
//public members
};
/**
* IF the given difficulty is valid and new it'll send it to the client. this.sendMiningJob = function(jobParams){
* returns boolean sendJson({
**/ id : null,
this.sendAndSetDifficultyIfNew = function(difficulty){ method: "mining.notify",
if (typeof(difficulty) != 'number') { params: jobParams
console.error('[StratumClient.sendAndSetDifficultyIfNew] given difficulty parameter is not a number: ['+difficulty+']'); });
return false; };
} };
StratumClient.prototype.__proto__ = events.EventEmitter.prototype;
if (difficulty !== this.difficulty) {
this.difficulty = difficulty;
sendJson({
id : null, /**
method: "mining.set_difficulty", * The actual stratum server.
params: [difficulty]//[512], * It emits the following Events:
}); * - 'client.connected'(StratumClientInstance) - when a new miner connects
return true; * - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore.
} else { * - 'started' - when the server is up and running
return false; **/
} var StratumServer = exports.Server = function StratumServer(options){
}; //private members
this.sendMiningJob = function(jobParams){ var _this = this;
sendJson({ var socketServer;
id : null, var stratumClients = {};
method: "mining.notify", var subscriptionCounter = SubscriptionCounter();
params: jobParams
}); (function init(){
}; _socketServer = socketServer = net.createServer(function(c){
}; var subscriptionId = subscriptionCounter.next();
StratumClient.prototype.__proto__ = events.EventEmitter.prototype; var client = new StratumClient(
{
subscriptionId : subscriptionId,
socket : c,
/** authorizeFn : options.authorizeFn
* The actual stratum server. }
* It emits the following Events: );
* - 'client.connected'(StratumClientInstance) - when a new miner connects stratumClients[subscriptionId] = client;
* - 'client.disconnected'(StratumClientInstance) - when a miner disconnects. Be aware that the socket cannot be used anymore. _this.emit('client.connected', client);
* - 'started' - when the server is up and running client.on('socketDisconnect', function() {
**/ delete stratumClients[subscriptionId];
var StratumServer = exports.Server = function StratumServer(options){ _this.emit('client.disconnected', client);
});
//private members });
_socketServer.listen(options.port, function(){
var _this = this; _this.emit('started');
var socketServer; });
var stratumClients = {}; })();
var subscriptionCounter = SubscriptionCounter();
(function init(){ //public members
_socketServer = socketServer = net.createServer(function(c){
var subscriptionId = subscriptionCounter.next(); this.broadcastMiningJobs = function(jobParams) {
var client = new StratumClient( for (var clientId in stratumClients) {
{ // if a client gets disconnected WHILE doing this loop a crash might happen.
subscriptionId : subscriptionId, // 'm not sure if that can ever happn but an if here doesn't hurt!
socket : c, if (typeof(stratumClients[clientId]) !== 'undefined') {
authorizeFn : options.authorizeFn stratumClients[clientId].sendMiningJob(jobParams);
} }
); }
stratumClients[subscriptionId] = client; };
_this.emit('client.connected', client); };
client.on('socketDisconnect', function() {
delete stratumClients[subscriptionId];
_this.emit('client.disconnected', client);
});
});
_socketServer.listen(options.port, function(){
_this.emit('started');
});
})();
//public members
this.broadcastMiningJobs = function(jobParams) {
for (var clientId in stratumClients) {
// 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);
}
}
};
};
StratumServer.prototype.__proto__ = events.EventEmitter.prototype; StratumServer.prototype.__proto__ = events.EventEmitter.prototype;

View File

@ -1,110 +1,110 @@
var binpack = require('binpack'); var binpack = require('binpack');
var buffertools = require('buffertools'); var buffertools = require('buffertools');
var util = require('./util.js'); var util = require('./util.js');
function Transaction(params){ function Transaction(params){
var version = params.version || 1, var version = params.version || 1,
inputs = params.inputs || [], inputs = params.inputs || [],
outputs = params.outputs || [], outputs = params.outputs || [],
lockTime = params.lockTime || 0; lockTime = params.lockTime || 0;
this.toBuffer = function(){ this.toBuffer = function(){
return Buffer.concat([ return Buffer.concat([
binpack.packUInt32(version, 'little'), binpack.packUInt32(version, 'little'),
util.varIntBuffer(inputs.length), util.varIntBuffer(inputs.length),
Buffer.concat(inputs.map(function(i){ return i.toBuffer() })), Buffer.concat(inputs.map(function(i){ return i.toBuffer() })),
util.varIntBuffer(outputs.length), util.varIntBuffer(outputs.length),
Buffer.concat(outputs.map(function(o){ return o.toBuffer() })), Buffer.concat(outputs.map(function(o){ return o.toBuffer() })),
binpack.packUInt32(lockTime, 'little') binpack.packUInt32(lockTime, 'little')
]); ]);
}; };
this.inputs = inputs; this.inputs = inputs;
this.outputs = outputs; this.outputs = outputs;
} }
function TransactionInput(params){ function TransactionInput(params){
var prevOutHash = params.prevOutHash || 0, var prevOutHash = params.prevOutHash || 0,
prevOutIndex = params.prevOutIndex, prevOutIndex = params.prevOutIndex,
sigScript = params.sigScript, sigScript = params.sigScript,
sequence = params.sequence || 0; sequence = params.sequence || 0;
this.toBuffer = function(){ this.toBuffer = function(){
sigScriptBuffer = sigScript.toBuffer(); sigScriptBuffer = sigScript.toBuffer();
return Buffer.concat([ return Buffer.concat([
util.uint256BufferFromHash(prevOutHash), util.uint256BufferFromHash(prevOutHash),
binpack.packUInt32(prevOutIndex, 'little'), binpack.packUInt32(prevOutIndex, 'little'),
util.varIntBuffer(sigScriptBuffer.length), util.varIntBuffer(sigScriptBuffer.length),
sigScriptBuffer, sigScriptBuffer,
binpack.packUInt32(sequence) binpack.packUInt32(sequence)
]); ]);
}; };
} }
function TransactionOutput(params){ function TransactionOutput(params){
var value = params.value, var value = params.value,
pkScriptBuffer = params.pkScriptBuffer; pkScriptBuffer = params.pkScriptBuffer;
this.toBuffer = function(){ this.toBuffer = function(){
return Buffer.concat([ return Buffer.concat([
binpack.packInt64(value, 'little'), binpack.packInt64(value, 'little'),
util.varIntBuffer(pkScriptBuffer.length), util.varIntBuffer(pkScriptBuffer.length),
pkScriptBuffer pkScriptBuffer
]); ]);
}; };
} }
function ScriptSig(params){ function ScriptSig(params){
var height = params.height, var height = params.height,
flags = params.flags, flags = params.flags,
extraNoncePlaceholder = params.extraNoncePlaceholder; extraNoncePlaceholder = params.extraNoncePlaceholder;
this.toBuffer = function(){ this.toBuffer = function(){
return Buffer.concat([ return Buffer.concat([
util.serializeNumber(height), util.serializeNumber(height),
new Buffer(flags, 'hex'), new Buffer(flags, 'hex'),
util.serializeNumber(Date.now() / 1000 | 0), util.serializeNumber(Date.now() / 1000 | 0),
new Buffer([extraNoncePlaceholder.length]), new Buffer([extraNoncePlaceholder.length]),
extraNoncePlaceholder, extraNoncePlaceholder,
util.serializeString('/nodeStratum/') util.serializeString('/nodeStratum/')
]); ]);
} }
}; };
var Generation = exports.Generation = function Generation(rpcData, publicKey, extraNoncePlaceholder){ var Generation = exports.Generation = function Generation(rpcData, publicKey, extraNoncePlaceholder){
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];
}; };

View File

@ -1,309 +1,309 @@
var crypto = require('crypto'); var crypto = require('crypto');
var binpack = require('binpack'); var binpack = require('binpack');
var base58 = require('base58-native'); var base58 = require('base58-native');
var bignum = require('bignum'); var bignum = require('bignum');
exports.bignumFromBits = function(bitsString){ exports.bignumFromBits = function(bitsString){
var bitsBuff = new Buffer(bitsString, 'hex'); var bitsBuff = new Buffer(bitsString, 'hex');
var numBytes = bitsBuff.readUInt8(0); var numBytes = bitsBuff.readUInt8(0);
var bigBits = bignum.fromBuffer(bitsBuff.slice(1)); var bigBits = bignum.fromBuffer(bitsBuff.slice(1));
var target = bigBits.mul( var target = bigBits.mul(
bignum(2).pow( bignum(2).pow(
bignum(8).mul( bignum(8).mul(
numBytes - 3 numBytes - 3
) )
) )
); );
return target; return target;
}; };
exports.bignumFromTarget = function(targetString){ exports.bignumFromTarget = function(targetString){
return bignum.fromBuffer(new Buffer(targetString, 'hex')); return bignum.fromBuffer(new Buffer(targetString, 'hex'));
}; };
exports.doublesha = function(buffer){ exports.doublesha = function(buffer){
var hash1 = crypto.createHash('sha256'); var hash1 = crypto.createHash('sha256');
hash1.update(buffer); hash1.update(buffer);
hash1 = hash1.digest(); hash1 = hash1.digest();
var hash2 = crypto.createHash('sha256'); var hash2 = crypto.createHash('sha256');
hash2.update(hash1); hash2.update(hash1);
hash2 = hash2.digest(); hash2 = hash2.digest();
return hash2; return hash2;
}; };
exports.reverseBuffer = function(buff){ exports.reverseBuffer = function(buff){
var reversed = new Buffer(buff.length); var reversed = new Buffer(buff.length);
for (var i = buff.length - 1; i >= 0; i--) for (var i = buff.length - 1; i >= 0; i--)
reversed[buff.length - i - 1] = buff[i]; reversed[buff.length - i - 1] = buff[i];
return reversed; return reversed;
}; };
exports.reverseHex = function(hex){ exports.reverseHex = function(hex){
return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex'); return exports.reverseBuffer(new Buffer(hex, 'hex')).toString('hex');
}; };
exports.reverseByteOrder = function(buff){ exports.reverseByteOrder = function(buff){
for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4); for (var i = 0; i < 8; i++) buff.writeUInt32LE(buff.readUInt32BE(i * 4), i * 4);
return exports.reverseBuffer(buff); return exports.reverseBuffer(buff);
}; };
exports.uint256BufferFromHash = function(hex){ exports.uint256BufferFromHash = function(hex){
var fromHex = new Buffer(hex, 'hex'); var fromHex = new Buffer(hex, 'hex');
if (fromHex.length != 32){ if (fromHex.length != 32){
var empty = new Buffer(32); var empty = new Buffer(32);
empty.fill(0); empty.fill(0);
fromHex.copy(empty); fromHex.copy(empty);
fromHex = empty; fromHex = empty;
} }
return exports.reverseBuffer(fromHex); return exports.reverseBuffer(fromHex);
}; };
exports.hexFromReversedBuffer = function(buffer){ exports.hexFromReversedBuffer = function(buffer){
return exports.reverseBuffer(buffer).toString('hex'); return exports.reverseBuffer(buffer).toString('hex');
}; };
exports.varIntBuffer = function(n){ exports.varIntBuffer = function(n){
if (n < 0xfd) if (n < 0xfd)
return new Buffer([n]); return new Buffer([n]);
else if (n < 0xffff){ else if (n < 0xffff){
var buff = new Buffer(3); var buff = new Buffer(3);
buff[0] = 0xfd; buff[0] = 0xfd;
buff.writeUInt16LE(n, 1); buff.writeUInt16LE(n, 1);
return buff; return buff;
} }
else if (n < 0xffffffff){ else if (n < 0xffffffff){
var buff = new Buffer(5); var buff = new Buffer(5);
buff[0] = 0xfe; buff[0] = 0xfe;
buff.writeUInt32LE(n, 1); buff.writeUInt32LE(n, 1);
return buff; return buff;
} }
else{ else{
var buff = new Buffer(9); var buff = new Buffer(9);
buff[0] = 0xff; buff[0] = 0xff;
binpack.packUInt64(n, 'little').copy(buff, 1); binpack.packUInt64(n, 'little').copy(buff, 1);
return buff; return buff;
} }
}; };
exports.serializeNumber = function(n){ exports.serializeNumber = function(n){
if (n < 0xfd){ if (n < 0xfd){
var buff = new Buffer(2); var buff = new Buffer(2);
buff[0] = 0x1; buff[0] = 0x1;
buff.writeUInt8(n, 1); buff.writeUInt8(n, 1);
return buff; return buff;
} }
else if (n <= 0xffff){ else if (n <= 0xffff){
var buff = new Buffer(4); var buff = new Buffer(4);
buff[0] = 0x3; buff[0] = 0x3;
buff.writeUInt16LE(n, 1); buff.writeUInt16LE(n, 1);
return buff; return buff;
} }
else if (n <= 0xffffffff){ else if (n <= 0xffffffff){
var buff = new Buffer(5); var buff = new Buffer(5);
buff[0] = 0x4; buff[0] = 0x4;
buff.writeUInt32LE(n, 1); buff.writeUInt32LE(n, 1);
return buff; return buff;
} }
else{ else{
return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]); return Buffer.concat([new Buffer([0x9]), binpack.packUInt64(n, 'little')]);
} }
}; };
exports.serializeString = function(s){ exports.serializeString = function(s){
if (s.length < 253) if (s.length < 253)
return Buffer.concat([ return Buffer.concat([
new Buffer([s.length]), new Buffer([s.length]),
new Buffer(s) new Buffer(s)
]); ]);
else if (s.length < 0x10000) else if (s.length < 0x10000)
return Buffer.concat([ return Buffer.concat([
new Buffer([253]), new Buffer([253]),
binpack.packUInt16(s.length, 'little'), binpack.packUInt16(s.length, 'little'),
new Buffer(s) new Buffer(s)
]); ]);
else if (s.length < 0x100000000) else if (s.length < 0x100000000)
return Buffer.concat([ return Buffer.concat([
new Buffer([254]), new Buffer([254]),
binpack.packUInt32(s.length, 'little'), binpack.packUInt32(s.length, 'little'),
new Buffer(s) new Buffer(s)
]); ]);
else else
return Buffer.concat([ return Buffer.concat([
new Buffer([255]), new Buffer([255]),
binpack.packUInt64(s.length), binpack.packUInt64(s.length),
new Buffer(s) new Buffer(s)
]); ]);
}; };
exports.range = function(start, stop, step){ exports.range = function(start, stop, step){
if (typeof stop === 'undefined'){ if (typeof stop === 'undefined'){
stop = start; stop = start;
start = 0; start = 0;
} }
if (typeof step === 'undefined'){ if (typeof step === 'undefined'){
step = 1; step = 1;
} }
if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){ if ((step > 0 && start >= stop) || (step < 0 && start <= stop)){
return []; return [];
} }
var result = []; var result = [];
for (var i = start; step > 0 ? i < stop : i > stop; i += step){ for (var i = start; step > 0 ? i < stop : i > stop; i += step){
result.push(i); result.push(i);
} }
return result; return result;
}; };
exports.address_to_pubkeyhash = function(addr){ exports.address_to_pubkeyhash = function(addr){
addr = base58.decode(addr); addr = base58.decode(addr);
if (addr.length != 25){ if (addr.length != 25){
console.log('invalid address length for ' + addr); console.log('invalid address length for ' + addr);
throw 'invalid address length'; throw 'invalid address length';
} }
if (!addr) if (!addr)
return null; return null;
var ver = addr[0]; var ver = addr[0];
var cksumA = addr.slice(-4); var cksumA = addr.slice(-4);
var cksumB = exports.doublesha(addr.slice(0, -4)).slice(0, 4); var cksumB = exports.doublesha(addr.slice(0, -4)).slice(0, 4);
if (cksumA.toString('hex') != cksumB.toString('hex')) if (cksumA.toString('hex') != cksumB.toString('hex'))
throw 'checksum did not match'; throw 'checksum did not match';
return [ver, addr.slice(1,-4)]; return [ver, addr.slice(1,-4)];
}; };
exports.script_to_pubkey = function(key){ exports.script_to_pubkey = function(key){
if (key.length === 66) key = new Buffer(key, 'hex'); if (key.length === 66) key = new Buffer(key, 'hex');
if (key !== 33) throw 'Invalid address'; if (key !== 33) throw 'Invalid address';
var pubkey = new Buffer(35); var pubkey = new Buffer(35);
pubkey[0] = 0x21; pubkey[0] = 0x21;
pubkey[34] = 0xac; pubkey[34] = 0xac;
key.copy(pubkey, 1); key.copy(pubkey, 1);
return pubkey; return pubkey;
}; };
exports.script_to_address = function(addr){ exports.script_to_address = function(addr){
var d = exports.address_to_pubkeyhash(addr) var d = exports.address_to_pubkeyhash(addr)
if (!d) if (!d)
throw "invalid address"; throw "invalid address";
var ver = d[0]; var ver = d[0];
var pubkeyhash = d[1]; var pubkeyhash = d[1];
return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkeyhash, new Buffer([0x88, 0xac])]); return Buffer.concat([new Buffer([0x76, 0xa9, 0x14]), pubkeyhash, new Buffer([0x88, 0xac])]);
}; };
/* /*
exports.makeBufferReadable = function(buffer){ exports.makeBufferReadable = function(buffer){
var position = 0; var position = 0;
buffer.read = function(length){ buffer.read = function(length){
var section = buffer.slice(position, length ? (position + length) : buffer.length); var section = buffer.slice(position, length ? (position + length) : buffer.length);
position += length; position += length;
return MakeBufferReadable(section); return MakeBufferReadable(section);
} }
return buffer; return buffer;
}; };
exports.ser_uint256 = function(u){ exports.ser_uint256 = function(u){
var rs = new Buffer(0); var rs = new Buffer(0);
exports.range(8).forEach(function(i){ exports.range(8).forEach(function(i){
rs = Buffer.concat([ rs = Buffer.concat([
rs, rs,
binpack.packUInt32(u & 0xFFFFFFFF, 'little') binpack.packUInt32(u & 0xFFFFFFFF, 'little')
]); ]);
u >>= 32; u >>= 32;
}); });
return rs; return rs;
}; };
exports.deser_uint256 = function(f){ exports.deser_uint256 = function(f){
var r = 0; var r = 0;
exports.range(8).forEach(function(i){ exports.range(8).forEach(function(i){
var t = f.read(4).readUInt32LE(4); var t = f.read(4).readUInt32LE(4);
r += t << (i * 32); r += t << (i * 32);
}); });
return r; return r;
}; };
exports.uint256_from_compact = function(c){ exports.uint256_from_compact = function(c){
var nbytes = (c >> 24) & 0xFF; var nbytes = (c >> 24) & 0xFF;
v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) v = (c & 0xFFFFFF) << (8 * (nbytes - 3))
return v; return v;
}; };
exports.uint256_from_str = function(s){ exports.uint256_from_str = function(s){
var r = 0; var r = 0;
var t = binpack.unpack var t = binpack.unpack
}; };
exports.ser_uint256_be = function(u){ exports.ser_uint256_be = function(u){
var rs = new Buffer(0); var rs = new Buffer(0);
exports.range(8).forEach(function(i){ exports.range(8).forEach(function(i){
rs = Buffer.concat([ rs = Buffer.concat([
rs, rs,
binpack.packUInt32(u & 0xFFFFFFFF, 'big') binpack.packUInt32(u & 0xFFFFFFFF, 'big')
]); ]);
u >>= 32; u >>= 32;
}); });
return rs; return rs;
}; };
exports.deser_string = function(f){ exports.deser_string = function(f){
var nit = f.read(1).readUInt8(0); var nit = f.read(1).readUInt8(0);
if (nit == 253) if (nit == 253)
nit = f.read(2).readUInt16LE(0); nit = f.read(2).readUInt16LE(0);
else if (nit == 254) else if (nit == 254)
nit = f.read(4).readUInt32LE(1); nit = f.read(4).readUInt32LE(1);
else if (nit == 255) else if (nit == 255)
nit = f.read(8).readUInt64LE(1); nit = f.read(8).readUInt64LE(1);
return f.read(nit); return f.read(nit);
}; };
exports.ser_vector = function(l){ exports.ser_vector = function(l){
var r; var r;
if (l.length < 253) if (l.length < 253)
r = new Buffer([l.length]); r = new Buffer([l.length]);
else if (l.length < 0x10000) else if (l.length < 0x10000)
r = Buffer.concat([new Buffer([253]), binpack.packUInt16(l.length, 'little')]); r = Buffer.concat([new Buffer([253]), binpack.packUInt16(l.length, 'little')]);
else if (l.length < 0x100000000) else if (l.length < 0x100000000)
r = Buffer.concat([new Buffer([254]), binpack.packUInt32(l.length, 'little')]); r = Buffer.concat([new Buffer([254]), binpack.packUInt32(l.length, 'little')]);
else else
r = Buffer.concat([new Buffer([255]), binpack.packUInt64(l.length, 'little')]); r = Buffer.concat([new Buffer([255]), binpack.packUInt64(l.length, 'little')]);
l.forEach(function(i){ l.forEach(function(i){
r = Buffer.concat([r, i.serialize()]); r = Buffer.concat([r, i.serialize()]);
}); });
return r; return r;
}; };
exports.deser_vector = function(f, c){ exports.deser_vector = function(f, c){
var nit = f.read(1).readUInt8(0); var nit = f.read(1).readUInt8(0);
if (nit == 253) if (nit == 253)
nit = f.read(2).readUInt16LE(0); nit = f.read(2).readUInt16LE(0);
else if (nit == 254) else if (nit == 254)
nit = f.read(4).readUInt32LE(0); nit = f.read(4).readUInt32LE(0);
else if (nit == 255) else if (nit == 255)
nit = f.read(8).readUInt64LE(0); nit = f.read(8).readUInt64LE(0);
var r = []; var r = [];
exports.range(nit).forEach(function(i){ exports.range(nit).forEach(function(i){
var t = new c(); var t = new c();
t.deserialize(f); t.deserialize(f);
r.push(t); r.push(t);
}); });
return r; return r;
}; };
*/ */

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