improvements
This commit is contained in:
parent
b8d8f88296
commit
7e5241a6e2
@ -11,14 +11,14 @@ Features (mostly untested)
|
|||||||
* Daemon interface
|
* Daemon interface
|
||||||
* Stratum TCP socket server
|
* Stratum TCP socket server
|
||||||
* Block template / job manager
|
* Block template / job manager
|
||||||
|
* Optimized generation transaction building
|
||||||
|
|
||||||
#### To do
|
#### To do
|
||||||
* Optimized generation transaction building
|
|
||||||
* Integrate with PostgreSQL database
|
|
||||||
* Handle share submissions
|
* Handle share submissions
|
||||||
* Payment processing module
|
* Payment processing module
|
||||||
* Support more algos (scrypt, scrypt-jane, quark)
|
* Support more algos (scrypt, scrypt-jane, quark)
|
||||||
* Statistics module
|
* Statistics module
|
||||||
|
* Integrate with PostgreSQL database
|
||||||
* Web frontend
|
* Web frontend
|
||||||
|
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ Requirements
|
|||||||
* [bignum](https://github.com/justmoon/node-bignum)
|
* [bignum](https://github.com/justmoon/node-bignum)
|
||||||
* [buffertools] (https://github.com/bnoordhuis/node-buffertools)
|
* [buffertools] (https://github.com/bnoordhuis/node-buffertools)
|
||||||
* [base58-native](https://github.com/gasteve/node-base58)
|
* [base58-native](https://github.com/gasteve/node-base58)
|
||||||
|
* [async](https://github.com/caolan/async)
|
||||||
|
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
|
|||||||
@ -6,7 +6,7 @@ var transactions = require('./transactions.js');
|
|||||||
var util = require('./util.js');
|
var util = require('./util.js');
|
||||||
|
|
||||||
|
|
||||||
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, address){
|
var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, publicKey, reward){
|
||||||
|
|
||||||
//private members
|
//private members
|
||||||
|
|
||||||
@ -30,11 +30,10 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, addr
|
|||||||
this.jobId = jobId;
|
this.jobId = jobId;
|
||||||
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.coinbase = new transactions.Generation(
|
this.generationTransaction = new transactions.Generation(
|
||||||
rpcData.coinbasevalue,
|
rpcData,
|
||||||
rpcData.coinbaseaux.flags,
|
publicKey,
|
||||||
rpcData.height,
|
reward
|
||||||
address
|
|
||||||
);
|
);
|
||||||
|
|
||||||
this.getJobParams = function(){
|
this.getJobParams = function(){
|
||||||
@ -42,8 +41,8 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, addr
|
|||||||
this.jobParams = [
|
this.jobParams = [
|
||||||
this.jobId,
|
this.jobId,
|
||||||
util.reverseHex(this.rpcData.previousblockhash),
|
util.reverseHex(this.rpcData.previousblockhash),
|
||||||
this.coinbase.serialized[0].toString('hex'),
|
this.generationTransaction.coinbase[0].toString('hex'),
|
||||||
this.coinbase.serialized[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,
|
||||||
|
|||||||
@ -73,7 +73,7 @@ function DaemonInterface(options){
|
|||||||
});
|
});
|
||||||
res.on('end', function(){
|
res.on('end', function(){
|
||||||
var dataJson = JSON.parse(data);
|
var dataJson = JSON.parse(data);
|
||||||
callback(null, dataJson);
|
callback(null, dataJson.result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -67,8 +67,8 @@ var JobManager = module.exports = function JobManager(options){
|
|||||||
|
|
||||||
this.extraNonceCounter = new ExtraNonceCounter();
|
this.extraNonceCounter = new ExtraNonceCounter();
|
||||||
this.currentJob;
|
this.currentJob;
|
||||||
this.newTemplate = function(rpcData){
|
this.newTemplate = function(rpcData, publicKey){
|
||||||
this.currentJob = new blockTemplate(jobCounter.next(), rpcData, options.address);
|
this.currentJob = new blockTemplate(jobCounter.next(), rpcData, publicKey);
|
||||||
jobs[this.currentJob.jobId] = this.currentJob;
|
jobs[this.currentJob.jobId] = this.currentJob;
|
||||||
CheckNewIfNewBlock(this.currentJob);
|
CheckNewIfNewBlock(this.currentJob);
|
||||||
};
|
};
|
||||||
|
|||||||
1
main.js
1
main.js
@ -12,6 +12,7 @@ var coins = [
|
|||||||
name: 'Dogecoin',
|
name: 'Dogecoin',
|
||||||
symbol: 'doge',
|
symbol: 'doge',
|
||||||
algorithm: 'scrypt',
|
algorithm: 'scrypt',
|
||||||
|
reward: 'POW', //or POS
|
||||||
address: 'D5uXR7F6bTCJKRZBqj1D4gyHF9MHAd5oNs',
|
address: 'D5uXR7F6bTCJKRZBqj1D4gyHF9MHAd5oNs',
|
||||||
daemon: {
|
daemon: {
|
||||||
bin: 'dogecoind',
|
bin: 'dogecoind',
|
||||||
|
|||||||
55
pool.js
55
pool.js
@ -2,6 +2,7 @@ var net = require('net');
|
|||||||
var events = require('events');
|
var events = require('events');
|
||||||
|
|
||||||
var bignum = require('bignum');
|
var bignum = require('bignum');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
var daemon = require('./daemon.js');
|
var daemon = require('./daemon.js');
|
||||||
var stratum = require('./stratum.js');
|
var stratum = require('./stratum.js');
|
||||||
@ -14,6 +15,7 @@ var transactions = require('./transactions.js');
|
|||||||
var pool = module.exports = function pool(coin){
|
var pool = module.exports = function pool(coin){
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
|
var publicKeyBuffer;
|
||||||
|
|
||||||
this.jobManager = new jobManager({
|
this.jobManager = new jobManager({
|
||||||
algorithm: coin.options.algorithm,
|
algorithm: coin.options.algorithm,
|
||||||
@ -26,15 +28,52 @@ var pool = module.exports = function pool(coin){
|
|||||||
|
|
||||||
this.daemon = new daemon.interface(coin.options.daemon);
|
this.daemon = new daemon.interface(coin.options.daemon);
|
||||||
this.daemon.on('online', function(){
|
this.daemon.on('online', function(){
|
||||||
this.cmd(
|
async.parallel({
|
||||||
'getblocktemplate',
|
rpcTemplate: function(callback){
|
||||||
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
|
_this.daemon.cmd('getblocktemplate',
|
||||||
function(error, response){
|
[{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}],
|
||||||
_this.jobManager.newTemplate(response.result);
|
function(error, result){
|
||||||
console.log(response.result);
|
if (error){
|
||||||
//console.log(_this.jobManager.currentJob.getJobParams());
|
console.log('getblocktemplate rpc error for ' + coin.options.name);
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
callback(null, result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
addressInfo: function(callback){
|
||||||
|
_this.daemon.cmd('validateaddress',
|
||||||
|
[coin.options.address],
|
||||||
|
function(error, result){
|
||||||
|
if (error){
|
||||||
|
console.log('validateaddress rpc error for ' + coin.options.name);
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
else if (!result.isvalid){
|
||||||
|
console.log('address is not valid for ' + coin.options.name);
|
||||||
|
callback(error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
callback(error, result);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
}, function(err, results){
|
||||||
|
if (err) return;
|
||||||
|
|
||||||
|
//console.log(results);
|
||||||
|
|
||||||
|
publicKeyBuffer = coin.options.reward === 'POW' ?
|
||||||
|
util.script_to_address(results.addressInfo.address) :
|
||||||
|
util.script_to_pubkey(results.addressInfo.pubkey);
|
||||||
|
|
||||||
|
_this.jobManager.newTemplate(results.rpcTemplate, publicKeyBuffer);
|
||||||
|
|
||||||
|
console.log(_this.jobManager.currentJob.getJobParams());
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}).on('startFailed', function(){
|
}).on('startFailed', function(){
|
||||||
console.log('Failed to start daemon for ' + coin.name);
|
console.log('Failed to start daemon for ' + coin.name);
|
||||||
});
|
});
|
||||||
|
|||||||
14
stratum.js
14
stratum.js
@ -8,7 +8,7 @@ var util = require('./util.js');
|
|||||||
|
|
||||||
var SubscriptionCounter = function(){
|
var SubscriptionCounter = function(){
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var padding = 'deadbeefdeadbeef';
|
var padding = 'deadbeefcafebabe';
|
||||||
return {
|
return {
|
||||||
next: function(){
|
next: function(){
|
||||||
count++;
|
count++;
|
||||||
@ -167,15 +167,15 @@ var StratumServer = exports.Server = function StratumServer(options){
|
|||||||
//private members
|
//private members
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var _socketServer;
|
var socketServer;
|
||||||
var _stratumClients = {};
|
var stratumClients = {};
|
||||||
var _subscriptionCounter = SubscriptionCounter();
|
var subscriptionCounter = SubscriptionCounter();
|
||||||
|
|
||||||
(function init(){
|
(function init(){
|
||||||
_socketServer = socketServer = net.createServer(function(c){
|
_socketServer = socketServer = net.createServer(function(c){
|
||||||
var subscriptionId = _subscriptionCounter.next();
|
var subscriptionId = subscriptionCounter.next();
|
||||||
var client = new StratumClient({subscriptionId: subscriptionId, socket: c});
|
var client = new StratumClient({subscriptionId: subscriptionId, socket: c});
|
||||||
_stratumClients[subscriptionId] = client;
|
stratumClients[subscriptionId] = client;
|
||||||
_this.emit('client', client);
|
_this.emit('client', client);
|
||||||
});
|
});
|
||||||
_socketServer.listen(options.port, function(){});
|
_socketServer.listen(options.port, function(){});
|
||||||
@ -186,7 +186,7 @@ var StratumServer = exports.Server = function StratumServer(options){
|
|||||||
|
|
||||||
this.broadcastMiningJobs = function(jobParams){
|
this.broadcastMiningJobs = function(jobParams){
|
||||||
for (var clientId in _stratumClients){
|
for (var clientId in _stratumClients){
|
||||||
_stratumClients[clientId].sendMiningJob(jobParams)
|
stratumClients[clientId].sendMiningJob(jobParams)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
182
transactions.js
182
transactions.js
@ -1,16 +1,153 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
Ported from https://github.com/slush0/stratum-mining
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
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');
|
||||||
|
|
||||||
|
|
||||||
|
var extranonce_placeholder = new Buffer('f000000ff111111f', 'hex');
|
||||||
|
exports.extranonce_size = extranonce_placeholder.length;
|
||||||
|
|
||||||
|
|
||||||
|
function Transaction(params){
|
||||||
|
var version;
|
||||||
|
var inputs;
|
||||||
|
var outputs;
|
||||||
|
var lockTime;
|
||||||
|
|
||||||
|
(function init(){
|
||||||
|
if (typeof(params) === "object"){
|
||||||
|
version = params.version || 1;
|
||||||
|
inputs = params.inputs || [];
|
||||||
|
outputs = params.outputs || [];
|
||||||
|
lockTime = params.lockTime || 0;
|
||||||
|
}
|
||||||
|
else if (typeof(params) === "string"){
|
||||||
|
fromRaw(params);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function fromRaw(raw){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toBuffer = function(){
|
||||||
|
return Buffer.concat([
|
||||||
|
binpack.packUInt32(version, 'little'),
|
||||||
|
util.varIntBuffer(inputs.length),
|
||||||
|
Buffer.concat(inputs.map(function(i){ return i.toBuffer() })),
|
||||||
|
util.varIntBuffer(outputs.length),
|
||||||
|
Buffer.concat(outputs.map(function(o){ return o.toBuffer() })),
|
||||||
|
binpack.packUInt32(lockTime, 'little')
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.inputs = inputs;
|
||||||
|
this.outputs = outputs;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransactionInput(params){
|
||||||
|
var prevOutHash;
|
||||||
|
var prevOutIndex;
|
||||||
|
var sigScriptBuffer;
|
||||||
|
var sequence;
|
||||||
|
|
||||||
|
(function init(){
|
||||||
|
if (typeof(params) === "object"){
|
||||||
|
prevOutHash = params.prevOutHash || 0;
|
||||||
|
prevOutIndex = params.prevOutIndex;
|
||||||
|
sigScriptBuffer = params.sigScriptBuffer;
|
||||||
|
sequence = params.sequence || 0;
|
||||||
|
}
|
||||||
|
else if (typeof(params) === "string"){
|
||||||
|
fromRaw(params);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function fromRaw(raw){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toBuffer = function(){
|
||||||
|
return Buffer.concat([
|
||||||
|
util.uint256BufferFromHash(prevOutHash),
|
||||||
|
binpack.packUInt32(prevOutIndex, 'little'),
|
||||||
|
util.varIntBuffer(sigScriptBuffer.length),
|
||||||
|
sigScriptBuffer,
|
||||||
|
binpack.packUInt32(sequence)
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function TransactionOutput(params){
|
||||||
|
|
||||||
|
var value;
|
||||||
|
var pkScriptBuffer;
|
||||||
|
|
||||||
|
(function init(){
|
||||||
|
if (typeof(params) === "object"){
|
||||||
|
value = params.value;
|
||||||
|
pkScriptBuffer = params.pkScriptBuffer;
|
||||||
|
}
|
||||||
|
else if (typeof(params) === "string"){
|
||||||
|
fromRaw(params);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
function fromRaw(raw){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toBuffer = function(){
|
||||||
|
return Buffer.concat([
|
||||||
|
binpack.packInt64(value, 'little'),
|
||||||
|
util.varIntBuffer(pkScriptBuffer.length),
|
||||||
|
pkScriptBuffer
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildScriptSig = function(height, flags){
|
||||||
|
return Buffer.concat([
|
||||||
|
util.serializeNumber(height),
|
||||||
|
new Buffer(flags, 'hex'),
|
||||||
|
util.serializeNumber(Date.now() / 1000 | 0),
|
||||||
|
new Buffer([exports.extranonce_size]),
|
||||||
|
extranonce_placeholder,
|
||||||
|
util.ser_string('/nodeStratum/')
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
var Generation = exports.Generation = function Generation(rpcData, publicKey){
|
||||||
|
|
||||||
|
var scriptSig = buildScriptSig(rpcData.height, rpcData.coinbaseaux.flags);
|
||||||
|
|
||||||
|
var tx = new Transaction({
|
||||||
|
inputs: [new TransactionInput({
|
||||||
|
prevOutIndex: Math.pow(2, 32) - 1,
|
||||||
|
sigScriptBuffer: scriptSig
|
||||||
|
})],
|
||||||
|
outputs: [new TransactionOutput({
|
||||||
|
value: rpcData.coinbasevalue,
|
||||||
|
pkScriptBuffer: publicKey
|
||||||
|
})]
|
||||||
|
});
|
||||||
|
|
||||||
|
var txBuffer = tx.toBuffer();
|
||||||
|
var epIndex = buffertools.indexOf(txBuffer, extranonce_placeholder);
|
||||||
|
var p1 = txBuffer.slice(0, epIndex);
|
||||||
|
var p2 = txBuffer.slice(epIndex + extranonce_placeholder.length);
|
||||||
|
|
||||||
|
this.transaction = tx;
|
||||||
|
this.coinbase = [p1, p2];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
function COutPoint(){
|
function COutPoint(){
|
||||||
this.hash = 0;
|
this.hash = 0;
|
||||||
this.n = 0;
|
this.n = 0;
|
||||||
@ -97,36 +234,6 @@ CTransaction.prototype = {
|
|||||||
exports.CTransaction = CTransaction;
|
exports.CTransaction = CTransaction;
|
||||||
|
|
||||||
|
|
||||||
var extranonce_placeholder = new Buffer('f000000ff111111f', 'hex');
|
|
||||||
exports.extranonce_size = extranonce_placeholder.length;
|
|
||||||
|
|
||||||
|
|
||||||
var GenerationNew = function(blockTemplate, address){
|
|
||||||
return Buffer.concat([
|
|
||||||
binpack.packInt32(1, 'little'), //transaction version
|
|
||||||
new Buffer([1]), //length of transaction inputs (which is 1, the coinbase)
|
|
||||||
Buffer.concat([ //serialized coinbase tx input
|
|
||||||
Buffer.concat([ //prevout
|
|
||||||
util.uint256BufferFromHash(0), //hash
|
|
||||||
binpack.packUInt32(Math.pow(2, 32) - 1, 'little') //index
|
|
||||||
]),
|
|
||||||
util.ser_string(Buffer.concat([ //script length (varint), script
|
|
||||||
Buffer.concat([
|
|
||||||
util.serializeNumber(blockTemplate.rpcData.height),
|
|
||||||
new Buffer(blockTemplate.rpcData.coinbaseaux.flags, 'hex'),
|
|
||||||
util.serializeNumber(Date.now() / 1000 | 0),
|
|
||||||
new Buffer([exports.extranonce_size])
|
|
||||||
]),
|
|
||||||
extranonce_placeholder,
|
|
||||||
util.ser_string('/stratum/')
|
|
||||||
])),
|
|
||||||
binpack.packUInt32(0, 'little') //sequence number
|
|
||||||
]),
|
|
||||||
util.ser_vector(this.vout),
|
|
||||||
binpack.packUInt32(0, 'little') //locktime
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var Generation = exports.Generation = function Generation(coinbaseValue, coinbaseAuxFlags, height, address){
|
var Generation = exports.Generation = function Generation(coinbaseValue, coinbaseAuxFlags, height, address){
|
||||||
var CTrans = new CTransaction();
|
var CTrans = new CTransaction();
|
||||||
|
|
||||||
@ -180,3 +287,4 @@ Generation.prototype = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
33
util.js
33
util.js
@ -194,6 +194,29 @@ exports.deser_string = function(f){
|
|||||||
return f.read(nit);
|
return f.read(nit);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.varIntBuffer = function(n){
|
||||||
|
if (n < 0xfd)
|
||||||
|
return new Buffer([n]);
|
||||||
|
else if (n < 0xffff){
|
||||||
|
var buff = new Buffer(3);
|
||||||
|
buff[0] = 0xfd;
|
||||||
|
buff.writeUInt16LE(n, 1);
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
else if (n < 0xffffffff){
|
||||||
|
var buff = new Buffer(5);
|
||||||
|
buff[0] = 0xfe;
|
||||||
|
buff.writeUInt32LE(n, 1);
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
var buff = new Buffer(9);
|
||||||
|
buff[0] = 0xff;
|
||||||
|
binpack.packUInt64(n, 'little').copy(buff, 1);
|
||||||
|
return buff;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
exports.ser_vector = function(l){
|
exports.ser_vector = function(l){
|
||||||
var r;
|
var r;
|
||||||
if (l.length < 253)
|
if (l.length < 253)
|
||||||
@ -268,6 +291,16 @@ exports.address_to_pubkeyhash = function(addr){
|
|||||||
return [ver, addr.slice(1,-4)];
|
return [ver, addr.slice(1,-4)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.script_to_pubkey = function(key){
|
||||||
|
if (key.length === 66) key = new Buffer(key, 'hex');
|
||||||
|
if (key !== 33) throw 'Invalid address';
|
||||||
|
var pubkey = new Buffer(35);
|
||||||
|
pubkey[0] = 0x21;
|
||||||
|
pubkey[34] = 0xac;
|
||||||
|
key.copy(pubkey, 1);
|
||||||
|
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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user