From a1cabfb8bdbc1339fabacd91a6e0b5fef8e9725c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 2 Jun 2016 01:01:54 -0700 Subject: [PATCH] miner. improve workers. --- lib/bcoin/block.js | 3 +- lib/bcoin/miner.js | 82 +++++++++++++++++++++++++++++++++++++------- lib/bcoin/peer.js | 5 --- lib/bcoin/pool.js | 4 ++- lib/bcoin/workers.js | 79 +++++++++++++++++++++++++++++------------- 5 files changed, 129 insertions(+), 44 deletions(-) diff --git a/lib/bcoin/block.js b/lib/bcoin/block.js index b9d4571d..aec06890 100644 --- a/lib/bcoin/block.js +++ b/lib/bcoin/block.js @@ -217,8 +217,7 @@ Block.prototype.addTX = function addTX(tx) { index = this.txs.push(tx) - 1; - if (!this.mutable) - tx.setBlock(this, index); + tx.setBlock(this, index); return tx; }; diff --git a/lib/bcoin/miner.js b/lib/bcoin/miner.js index 19be189c..77102bee 100644 --- a/lib/bcoin/miner.js +++ b/lib/bcoin/miner.js @@ -11,6 +11,8 @@ var assert = utils.assert; var constants = bcoin.protocol.constants; var bn = require('bn.js'); var EventEmitter = require('events').EventEmitter; +var BufferReader = require('./reader'); +var BufferWriter = require('./writer'); /** * A bitcoin miner (supports mining witness blocks). @@ -19,8 +21,6 @@ var EventEmitter = require('events').EventEmitter; * @param {Object} options * @param {Base58Address} options.address - Payout address. * @param {String?} [options.coinbaseFlags="mined by bcoin"] - * @param {Function?} dsha256 - Optional sha256 substitute - * for faster linked code. * @property {Boolean} running * @property {Boolean} loaded * @emits Miner#block @@ -40,10 +40,6 @@ function Miner(options) { this.address = this.options.address; this.coinbaseFlags = this.options.coinbaseFlags || 'mined by bcoin'; - // Allow a dsha256 option in case someone - // wants to pass in a faster linked in function. - this.dsha256 = this.options.dsha256 || utils.dsha256; - this.pool = options.pool; this.chain = options.chain; this.mempool = options.mempool; @@ -101,14 +97,14 @@ Miner.prototype._init = function _init() { if (!self.running) return; if (self.attempt) - self.attempt.addTX(tx); + self.attempt.addTX(tx.clone()); }); } else if (this.pool) { this.pool.on('tx', function(tx) { if (!self.running) return; if (self.attempt) - self.attempt.addTX(tx); + self.attempt.addTX(tx.clone()); }); } @@ -272,7 +268,6 @@ Miner.prototype.createBlock = function createBlock(version, callback) { coinbaseFlags: self.coinbaseFlags, witness: self.chain.segwitActive, parallel: self.options.parallel, - dsha256: self.dsha256, network: self.network }); @@ -337,7 +332,6 @@ Miner.prototype.mineBlock = function mineBlock(version, callback) { * @param {ChainEntry} options.tip * @param {Number} options.height * @param {Number} options.target - Compact form. - * @param {Function} options.dsha256 * @param {Base58Address} options.address - Payout address. * @param {Boolean} options.witness - Allow witness * transactions, mine a witness block. @@ -350,6 +344,8 @@ Miner.prototype.mineBlock = function mineBlock(version, callback) { */ function MinerBlock(options) { + var i; + if (!(this instanceof MinerBlock)) return new MinerBlock(options); @@ -360,7 +356,6 @@ function MinerBlock(options) { this.target = utils.fromCompact(options.target).toBuffer('le', 32); this.extraNonce = new bn(0); this.iterations = 0; - this.dsha256 = options.dsha256; this.coinbaseFlags = options.coinbaseFlags; this.witness = options.witness; this.address = options.address; @@ -415,6 +410,11 @@ function MinerBlock(options) { this.block.addTX(this.coinbase); + if (options.txs) { + for (i = 0; i < options.txs.length; i++) + this.block.addTX(options.txs[i]); + } + if (this.witness) { // Set up the witness nonce and // commitment output for segwit. @@ -526,7 +526,7 @@ MinerBlock.prototype.findNonce = function findNonce() { // The heart and soul of the miner: match the target. while (block.nonce <= 0xffffffff) { // Hash and test against the next target - if (rcmp(this.dsha256(data), target) < 0) + if (rcmp(utils.dsha256(data), target) < 0) return true; // Increment the nonce to get a different hash @@ -665,6 +665,64 @@ MinerBlock.prototype.destroy = function destroy() { this.block = null; }; +/** + * Serialize the miner block. + * @returns {Buffer} + */ + +MinerBlock.prototype.toRaw = function toRaw(writer) { + var p = new BufferWriter(writer); + var i; + + p.writeBytes(this.tip.toRaw()); + p.writeU32(this.block.version); + p.writeU32(this.block.bits); + p.writeVarString(this.address, 'ascii'); + p.writeVarString(this.coinbaseFlags, 'utf8'); + p.writeU8(this.witness ? 1 : 0); + p.writeVarint(this.block.txs.length - 1); + + for (i = 1; i < this.block.txs.length; i++) + p.writeBytes(this.block.txs[i].render()); + + if (!writer) + p = p.render(); + + return p; +}; + +/** + * Instantiate a miner block from serialized data. + * @params {Buffer} data + * @returns {MinerBlock} + */ + +MinerBlock.fromRaw = function fromRaw(data) { + var p = new BufferReader(data); + var tip = bcoin.chainentry.fromRaw(null, p); + var version = p.readU32(); + var bits = p.readU32(); + var address = p.readVarString('ascii'); + var coinbaseFlags = p.readVarString('utf8'); + var witness = p.readU8() === 1; + var count = p.readVarint(); + var txs = []; + var i; + + for (i = 0; i < count; i++) + txs.push(bcoin.tx.fromRaw(p)); + + return new MinerBlock({ + tip: tip, + version: version, + target: bits, + address: address, + coinbaseFlags: coinbaseFlags, + witness: witness, + txs: txs + }); +}; + /** * "Reverse" comparison so we don't have * to waste time reversing the block hash. diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index b9a6be92..dc43d98c 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -1274,11 +1274,6 @@ Peer.prototype._handleGetData = function _handleGetData(items) { return next(); } - if (tx.isCoinbase()) { - notfound.push({ type: constants.inv.TX, hash: hash }); - return next(); - } - data = witness ? self.framer.witnessTX(tx) : self.framer.tx(tx); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 8ed672a5..5d645c67 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -2413,7 +2413,7 @@ BroadcastItem.prototype.finish = function finish(err) { assert(this.timeout, 'Already finished.'); assert(this.pool.inv.map[this.hash], 'Already finished.'); - clearInterval(this.timeout); + clearTimeout(this.timeout); this.timeout = null; delete this.pool.inv.map[this.hash]; @@ -2443,6 +2443,8 @@ BroadcastItem.prototype.send = function send(peer, witness) { if (this.type === constants.inv.TX) { if (this.msg.isCoinbase()) { peer.write(peer.framer.notFound([this])); + bcoin.debug('Failsafe: tried to relay a coinbase.'); + this.finish(new Error('Coinbase.')); return true; } diff --git a/lib/bcoin/workers.js b/lib/bcoin/workers.js index f8a32fbb..bd53edbc 100644 --- a/lib/bcoin/workers.js +++ b/lib/bcoin/workers.js @@ -68,6 +68,52 @@ Workers.cleanup = function cleanup() { Workers._exitBound = false; +/** + * Bind to process events in order to cleanup listeners. + * @private + */ + +Workers._bindExit = function _bindExit() { + if (utils.isBrowser) + return; + + if (Workers._exitBound) + return; + + Workers._exitBound = true; + + function onExit(err) { + Workers.cleanup(); + if (err) { + console.error(err.stack + ''); + process.exit(1); + return; + } + process.exit(0); + } + + process.once('exit', function() { + Workers.cleanup(); + }); + + if (process.listeners('SIGINT').length === 0) + process.once('SIGINT', onExit); + + if (process.listeners('SIGTERM').length === 0) + process.once('SIGTERM', onExit); + + if (process.listeners('uncaughtException').length === 0) + process.once('uncaughtException', onExit); + + process.on('newListener', function(name) { + if (name === 'SIGINT' + || name === 'SIGTERM' + || name === 'uncaughtException') { + process.removeListener(name, onExit); + } + }); +}; + /** * Spawn a new worker. * @param {Number} id - Worker ID. @@ -110,10 +156,7 @@ Workers.prototype.spawn = function spawn(id) { Workers.children.push(child); - if (!Workers._exitBound) { - process.once('exit', Workers.cleanup); - Workers._exitBound = true; - } + Workers._bindExit(); return child; }; @@ -221,15 +264,7 @@ Workers.prototype.verify = function verify(tx, index, force, flags, callback) { */ Workers.prototype.mine = function mine(attempt, callback) { - var data = { - tip: attempt.tip, - version: attempt.block.version, - target: attempt.block.bits, - address: attempt.address, - coinbaseFlags: attempt.coinbaseFlags, - witness: attempt.witness - }; - return this.execute('mine', [data], -1, callback); + return this.execute('mine', [attempt], -1, callback); }; /** @@ -679,16 +714,7 @@ jobs.verify = function verify(tx, index, force, flags) { * @returns {Block} */ -jobs.mine = function mine(data) { - var attempt = new bcoin.minerblock({ - tip: data.tip, - version: data.version, - target: data.target, - address: data.address, - coinbaseFlags: data.coinbaseFlags, - witness: data.witness, - dsha256: utils.dsha256 - }); +jobs.mine = function mine(attempt) { attempt.on('status', function(stat) { bcoin.debug( 'Miner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s', @@ -793,6 +819,9 @@ Framer.item = function _item(item, p) { } else if (item instanceof bcoin.mempoolentry) { p.writeU8(44); item.toRaw(p); + } else if (item instanceof bcoin.minerblock) { + p.writeU8(45); + item.toRaw(p); } else if (bn.isBN(item)) { p.writeU8(50); p.writeVarBytes(item.toBuffer()); @@ -951,8 +980,10 @@ Parser.parseItem = function parseItem(p) { return bcoin.coin.fromExtended(p); case 43: return bcoin.chainentry.fromRaw(null, p); - case 43: + case 44: return bcoin.mempoolentry.fromRaw(p); + case 45: + return bcoin.minerblock.fromRaw(p); case 50: return new bn(p.readVarBytes()); default: