diff --git a/README.md b/README.md index 9ee725d..b58be19 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ Features (mostly untested) * Daemon interface * Stratum TCP socket server * Block template / job manager +* Optimized generation transaction building #### To do -* Optimized generation transaction building -* Integrate with PostgreSQL database * Handle share submissions * Payment processing module * Support more algos (scrypt, scrypt-jane, quark) * Statistics module +* Integrate with PostgreSQL database * Web frontend @@ -32,6 +32,7 @@ Requirements * [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) Credits diff --git a/blockTemplate.js b/blockTemplate.js index ff12a1b..9702ba3 100644 --- a/blockTemplate.js +++ b/blockTemplate.js @@ -6,7 +6,7 @@ var transactions = require('./transactions.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 @@ -30,11 +30,10 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, addr this.jobId = jobId; this.merkleTree = new merkleTree(getTransactionBuffers(rpcData.transactions)); this.merkleBranch = getMerkleHashes(this.merkleTree.steps); - this.coinbase = new transactions.Generation( - rpcData.coinbasevalue, - rpcData.coinbaseaux.flags, - rpcData.height, - address + this.generationTransaction = new transactions.Generation( + rpcData, + publicKey, + reward ); this.getJobParams = function(){ @@ -42,8 +41,8 @@ var BlockTemplate = module.exports = function BlockTemplate(jobId, rpcData, addr this.jobParams = [ this.jobId, util.reverseHex(this.rpcData.previousblockhash), - this.coinbase.serialized[0].toString('hex'), - this.coinbase.serialized[1].toString('hex'), + this.generationTransaction.coinbase[0].toString('hex'), + this.generationTransaction.coinbase[1].toString('hex'), this.merkleBranch, binpack.packInt32(this.rpcData.version, 'big').toString('hex'), this.rpcData.bits, diff --git a/daemon.js b/daemon.js index 9f8337f..01033c1 100644 --- a/daemon.js +++ b/daemon.js @@ -73,7 +73,7 @@ function DaemonInterface(options){ }); res.on('end', function(){ var dataJson = JSON.parse(data); - callback(null, dataJson); + callback(null, dataJson.result); }); }); diff --git a/jobManager.js b/jobManager.js index e50ad5c..31c316b 100644 --- a/jobManager.js +++ b/jobManager.js @@ -67,8 +67,8 @@ var JobManager = module.exports = function JobManager(options){ this.extraNonceCounter = new ExtraNonceCounter(); this.currentJob; - this.newTemplate = function(rpcData){ - this.currentJob = new blockTemplate(jobCounter.next(), rpcData, options.address); + this.newTemplate = function(rpcData, publicKey){ + this.currentJob = new blockTemplate(jobCounter.next(), rpcData, publicKey); jobs[this.currentJob.jobId] = this.currentJob; CheckNewIfNewBlock(this.currentJob); }; diff --git a/main.js b/main.js index 7b737ee..d6553e2 100644 --- a/main.js +++ b/main.js @@ -12,6 +12,7 @@ var coins = [ name: 'Dogecoin', symbol: 'doge', algorithm: 'scrypt', + reward: 'POW', //or POS address: 'D5uXR7F6bTCJKRZBqj1D4gyHF9MHAd5oNs', daemon: { bin: 'dogecoind', diff --git a/pool.js b/pool.js index c81f696..f914fd9 100644 --- a/pool.js +++ b/pool.js @@ -2,6 +2,7 @@ var net = require('net'); var events = require('events'); var bignum = require('bignum'); +var async = require('async'); var daemon = require('./daemon.js'); var stratum = require('./stratum.js'); @@ -14,6 +15,7 @@ var transactions = require('./transactions.js'); var pool = module.exports = function pool(coin){ var _this = this; + var publicKeyBuffer; this.jobManager = new jobManager({ 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.on('online', function(){ - this.cmd( - 'getblocktemplate', - [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], - function(error, response){ - _this.jobManager.newTemplate(response.result); - console.log(response.result); - //console.log(_this.jobManager.currentJob.getJobParams()); + async.parallel({ + rpcTemplate: function(callback){ + _this.daemon.cmd('getblocktemplate', + [{"capabilities": [ "coinbasetxn", "workid", "coinbase/append" ]}], + function(error, result){ + if (error){ + 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(){ console.log('Failed to start daemon for ' + coin.name); }); diff --git a/stratum.js b/stratum.js index a195770..de8fb94 100644 --- a/stratum.js +++ b/stratum.js @@ -8,7 +8,7 @@ var util = require('./util.js'); var SubscriptionCounter = function(){ var count = 0; - var padding = 'deadbeefdeadbeef'; + var padding = 'deadbeefcafebabe'; return { next: function(){ count++; @@ -167,15 +167,15 @@ var StratumServer = exports.Server = function StratumServer(options){ //private members var _this = this; - var _socketServer; - var _stratumClients = {}; - var _subscriptionCounter = SubscriptionCounter(); + var socketServer; + var stratumClients = {}; + var subscriptionCounter = SubscriptionCounter(); (function init(){ _socketServer = socketServer = net.createServer(function(c){ - var subscriptionId = _subscriptionCounter.next(); + var subscriptionId = subscriptionCounter.next(); var client = new StratumClient({subscriptionId: subscriptionId, socket: c}); - _stratumClients[subscriptionId] = client; + stratumClients[subscriptionId] = client; _this.emit('client', client); }); _socketServer.listen(options.port, function(){}); @@ -186,7 +186,7 @@ var StratumServer = exports.Server = function StratumServer(options){ this.broadcastMiningJobs = function(jobParams){ for (var clientId in _stratumClients){ - _stratumClients[clientId].sendMiningJob(jobParams) + stratumClients[clientId].sendMiningJob(jobParams) } }; }; diff --git a/transactions.js b/transactions.js index 4035d2a..b6672cd 100644 --- a/transactions.js +++ b/transactions.js @@ -1,16 +1,153 @@ -/* - -Ported from https://github.com/slush0/stratum-mining - - */ - - var binpack = require('binpack'); var buffertools = require('buffertools'); 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(){ this.hash = 0; this.n = 0; @@ -97,36 +234,6 @@ CTransaction.prototype = { 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 CTrans = new CTransaction(); @@ -179,4 +286,5 @@ Generation.prototype = { ]); } -}; \ No newline at end of file +}; +*/ \ No newline at end of file diff --git a/util.js b/util.js index 09df6ec..adf4cd3 100644 --- a/util.js +++ b/util.js @@ -194,6 +194,29 @@ exports.deser_string = function(f){ 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){ var r; if (l.length < 253) @@ -268,6 +291,16 @@ exports.address_to_pubkeyhash = function(addr){ 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){ var d = exports.address_to_pubkeyhash(addr) if (!d)