Updated structure
This commit is contained in:
parent
2b5aefd7b4
commit
cea68b771f
12
README.md
12
README.md
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
264
index.js
@ -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;
|
||||||
@ -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 ,''));
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
@ -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;
|
||||||
@ -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
226
lib/pool.js
Normal 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;
|
||||||
@ -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;
|
||||||
@ -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];
|
||||||
|
|
||||||
};
|
};
|
||||||
@ -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
2
node_modules/bignum/package.json
generated
vendored
@ -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"
|
||||||
|
|||||||
2
node_modules/binpack/node_modules/bindings/bindings.js
generated
vendored
2
node_modules/binpack/node_modules/bindings/bindings.js
generated
vendored
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
2
node_modules/quark-hash/node_modules/bindings/bindings.js
generated
vendored
2
node_modules/quark-hash/node_modules/bindings/bindings.js
generated
vendored
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
2
node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js
generated
vendored
2
node_modules/scrypt-jane-hash/node_modules/bindings/bindings.js
generated
vendored
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
2
node_modules/scrypt256-hash/node_modules/bindings/bindings.js
generated
vendored
2
node_modules/scrypt256-hash/node_modules/bindings/bindings.js
generated
vendored
@ -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
39
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user