From acae8380597ad5984a4cb3d689b9d1187bcef5a6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 5 Oct 2016 16:16:08 -0700 Subject: [PATCH] miner: refactor jobs and pool. --- lib/miner/index.js | 1 + lib/miner/mine.js | 69 +++++++++ lib/miner/miner.js | 21 --- lib/miner/minerblock.js | 334 +++++++++++++++++----------------------- lib/workers/framer.js | 27 ++-- lib/workers/jobs.js | 19 ++- lib/workers/parser.js | 14 +- lib/workers/workers.js | 30 ++-- test/chain-test.js | 4 +- 9 files changed, 254 insertions(+), 265 deletions(-) create mode 100644 lib/miner/mine.js diff --git a/lib/miner/index.js b/lib/miner/index.js index 7779ae5d..40829c9d 100644 --- a/lib/miner/index.js +++ b/lib/miner/index.js @@ -1,4 +1,5 @@ 'use strict'; +exports.mine = require('./mine'); exports.Miner = require('./miner'); exports.MinerBlock = require('./minerblock'); diff --git a/lib/miner/mine.js b/lib/miner/mine.js new file mode 100644 index 00000000..7c9ef031 --- /dev/null +++ b/lib/miner/mine.js @@ -0,0 +1,69 @@ +/*! + * mine.js - mining function for bcoin + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +var crypto = require('../crypto/crypto'); +var assert = require('assert'); + +/** + * Hash until the nonce overflows. + * @param {Buffer} data + * @param {Buffer} target - Big endian. + * @param {Number} min + * @param {Number} max + * @returns {Number} Nonce or -1. + */ + +function mine(data, target, min, max) { + var nonce = min; + + data.writeUInt32LE(nonce, 76, true); + + // The heart and soul of the miner: match the target. + while (nonce <= max) { + // Hash and test against the next target. + if (rcmp(crypto.hash256(data), target) <= 0) + return nonce; + + // Increment the nonce to get a different hash + nonce++; + + // Update the raw buffer (faster than + // constantly serializing the headers). + data.writeUInt32LE(nonce, 76, true); + } + + return -1; +} + +/** + * "Reverse" comparison so we don't have + * to waste time reversing the block hash. + * @memberof Miner + * @param {Buffer} a + * @param {Buffer} b + * @returns {Number} + */ + +function rcmp(a, b) { + var i; + + assert(a.length === b.length); + + for (i = a.length - 1; i >= 0; i--) { + if (a[i] < b[i]) + return -1; + if (a[i] > b[i]) + return 1; + } + + return 0; +} + +/* + * Expose + */ + +module.exports = mine; diff --git a/lib/miner/miner.js b/lib/miner/miner.js index 682f6e46..6bafa6be 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -13,7 +13,6 @@ var assert = require('assert'); var AsyncObject = require('../utils/async'); var MinerBlock = require('./minerblock'); var Address = require('../primitives/address'); -var Workers = require('../workers/workers'); var time = require('../net/timedata'); /** @@ -58,7 +57,6 @@ function Miner(options) { this.running = false; this.timeout = null; this.attempt = null; - this.workerPool = null; this._init(); } @@ -113,21 +111,6 @@ Miner.prototype._init = function _init() { stat.height, stat.best); }); - - if (Workers.enabled) { - this.workerPool = new Workers({ - size: 1, - timeout: -1 - }); - - this.workerPool.on('error', function(err) { - self.emit('error', err); - }); - - this.workerPool.on('status', function(stat) { - self.emit('status', stat); - }); - } }; /** @@ -227,9 +210,6 @@ Miner.prototype.stop = function stop() { this.attempt.destroy(); this.attempt = null; } - - if (this.workerPool) - this.workerPool.destroy(); }; /** @@ -262,7 +242,6 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { } attempt = new MinerBlock({ - workerPool: this.workerPool, tip: tip, version: version, target: target, diff --git a/lib/miner/minerblock.js b/lib/miner/minerblock.js index 6f6edaf3..c469b0db 100644 --- a/lib/miner/minerblock.js +++ b/lib/miner/minerblock.js @@ -1,5 +1,5 @@ /*! - * miner.js - inefficient miner for bcoin (because we can) + * minerblock.js - miner block object for bcoin (because we can) * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin @@ -15,15 +15,13 @@ var constants = require('../protocol/constants'); var Network = require('../protocol/network'); var bn = require('bn.js'); var EventEmitter = require('events').EventEmitter; -var BufferReader = require('../utils/reader'); -var BufferWriter = require('../utils/writer'); var TX = require('../primitives/tx'); -var Address = require('../primitives/address'); var Block = require('../primitives/block'); var Input = require('../primitives/input'); var Output = require('../primitives/output'); var time = require('../net/timedata'); -var ChainEntry = require('../chain/chainentry'); +var mine = require('./mine'); +var workers = require('../workers/workers'); /** * MinerBlock @@ -62,10 +60,7 @@ function MinerBlock(options) { this.witness = options.witness; this.address = options.address; this.network = Network.get(options.network); - this.timeout = null; - - if (typeof this.coinbaseFlags === 'string') - this.coinbaseFlags = new Buffer(this.coinbaseFlags, 'utf8'); + this.destroyed = false; this.coinbase = new TX(); this.coinbase.mutable = true; @@ -78,6 +73,22 @@ function MinerBlock(options) { utils.inherits(MinerBlock, EventEmitter); +/** + * Nonce range interval. + * @const {Number} + * @default + */ + +MinerBlock.INTERVAL = 0xffffffff / 1500 | 0; + +MinerBlock.prototype.__defineGetter__('hashes', function() { + return this.iterations * 0xffffffff + this.block.nonce; +}); + +MinerBlock.prototype.__defineGetter__('rate', function() { + return (this.block.nonce / (utils.now() - this.begin)) | 0; +}); + /** * Initialize the block. * @private @@ -265,43 +276,137 @@ MinerBlock.prototype.addTX = function addTX(tx) { }; /** - * Hash until the nonce overflows, increment extraNonce, rebuild merkletree. + * Hash until the nonce overflows. * @returns {Boolean} Whether the nonce was found. */ MinerBlock.prototype.findNonce = function findNonce() { - var tip = this.tip; var block = this.block; var target = this.target; var data = block.abbr(); - var now; + var interval = MinerBlock.INTERVAL; + var min = 0; + var max = interval; + var nonce; + + while (max <= 0xffffffff) { + nonce = mine(data, target, min, max); + + if (nonce !== -1) + break; + + block.nonce = max; + + min += interval; + max += interval; + + this.sendStatus(); + } + + return nonce; +}; + +/** + * Hash until the nonce overflows. + * @returns {Boolean} Whether the nonce was found. + */ + +MinerBlock.prototype.findNonceAsync = co(function* findNonceAsync() { + var block = this.block; + var target = this.target; + var data = block.abbr(); + var interval = MinerBlock.INTERVAL; + var min = 0; + var max = interval; + var nonce; + + while (max <= 0xffffffff) { + nonce = yield workers.pool.mine(data, target, min, max); + + if (this.destroyed) + throw new Error('Miner was interrupted.'); + + if (nonce !== -1) + break; + + block.nonce = max; + + min += interval; + max += interval; + + this.sendStatus(); + } + + return nonce; +}); + +/** + * Mine synchronously until the block is found. + * @returns {Block} + */ + +MinerBlock.prototype.mine = function mine() { + var nonce; // Track how long we've been at it. this.begin = utils.now(); - assert(block.ts > tip.ts); + assert(this.block.ts > this.tip.ts); - // The heart and soul of the miner: match the target. - while (block.nonce <= 0xffffffff) { - // Hash and test against the next target. - if (rcmp(crypto.hash256(data), target) <= 0) { - this.coinbase.mutable = false; - this.block.mutable = false; - return true; - } + for (;;) { + nonce = this.findNonce(); - // Increment the nonce to get a different hash - block.nonce++; + if (nonce !== -1) + break; - // Update the raw buffer (faster than - // constantly serializing the headers). - data.writeUInt32LE(block.nonce, 76, true); - - // Send progress report every so often. - if (block.nonce % 500000 === 0) - this.sendStatus(); + this.iterate(); } + this.block.nonce = nonce; + this.block.mutable = false; + this.coinbase.mutable = false; + + return this.block; +}; + +/** + * Mine asynchronously until the block is found. + * @returns {Promise} - Returns {@link Block}. + */ + +MinerBlock.prototype.mineAsync = co(function* mineAsync() { + var nonce; + + // Track how long we've been at it. + this.begin = utils.now(); + + assert(this.block.ts > this.tip.ts); + + for (;;) { + nonce = yield this.findNonceAsync(); + + if (nonce !== -1) + break; + + this.iterate(); + } + + this.block.nonce = nonce; + this.block.mutable = false; + this.coinbase.mutable = false; + + return this.block; +}); + +/** + * Increment extraNonce, rebuild merkletree. + */ + +MinerBlock.prototype.iterate = function iterate() { + var block = this.block; + var tip = this.tip; + var now = time.now(); + // Keep track of our iterations. this.iterations++; @@ -313,28 +418,17 @@ MinerBlock.prototype.findNonce = function findNonce() { // update the timestamp. This improves // performance because we do not have to // recalculate the merkle root. - now = time.now(); if (now > block.ts && now > tip.ts) { block.ts = now; // Overflow the nonce block.nonce = 0; - return false; + return; } // Overflow the nonce and increment the extraNonce. this.updateNonce(); - - return false; }; -MinerBlock.prototype.__defineGetter__('hashes', function() { - return this.iterations * 0xffffffff + this.block.nonce; -}); - -MinerBlock.prototype.__defineGetter__('rate', function() { - return (this.block.nonce / (utils.now() - this.begin)) | 0; -}); - /** * Send a progress report (emits `status`). */ @@ -351,165 +445,13 @@ MinerBlock.prototype.sendStatus = function sendStatus() { }; /** - * Mine until the block is found. Will take a breather - * for 100ms every time the nonce overflows. - * @returns {Promise} - Returns {@link Block}. - */ - -MinerBlock.prototype.mine = co(function* mine() { - yield this.wait(100); - - // Try to find a block: do one iteration of extraNonce - if (!this.findNonce()) { - yield this.mine(); - return; - } - - return this.block; -}); - -/** - * Wait for a timeout. - * @param {Number} time - * @returns {Promise} - */ - -MinerBlock.prototype.wait = function wait(time) { - var self = this; - return new Promise(function(resolve, reject) { - self.timeout = setTimeout(function() { - resolve(); - }, time); - }); -}; - -/** - * Mine synchronously until the block is found. - * @returns {Block} - */ - -MinerBlock.prototype.mineSync = function mineSync() { - while (!this.findNonce()); - return this.block; -}; - -/** - * Attempt to mine the block on the worker pool. - * @returns {Promise} - Returns {@link Block}. - */ - -MinerBlock.prototype.mineAsync = co(function* mineAsync() { - var block; - - if (!this.workerPool) - return yield this.mine(); - - block = yield this.workerPool.mine(this); - - this.workerPool.destroy(); - - return block; -}); - -/** - * Destroy the minerblock. Stop mining. Clear timeout. + * Destroy the minerblock. Stop mining. */ MinerBlock.prototype.destroy = function destroy() { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } - this.block = null; + this.destroyed = true; }; -/** - * Serialize the miner block. - * @returns {Buffer} - */ - -MinerBlock.prototype.toRaw = function toRaw(writer) { - var p = new BufferWriter(writer); - var i; - - p.writeU32(this.network.magic); - p.writeBytes(this.tip.toRaw()); - p.writeU32(this.block.version); - p.writeU32(this.block.bits); - p.writeVarBytes(this.address.toRaw()); - p.writeVarBytes(this.coinbaseFlags); - 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].toRaw()); - - 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 network = Network.fromMagic(p.readU32()); - var tip = ChainEntry.fromRaw(null, p); - var version = p.readU32(); - var bits = p.readU32(); - var address = Address.fromRaw(p.readVarBytes()); - var coinbaseFlags = p.readVarBytes(); - var witness = p.readU8() === 1; - var count = p.readVarint(); - var txs = []; - var i; - - for (i = 0; i < count; i++) - txs.push(TX.fromRaw(p)); - - tip.network = network; - - return new MinerBlock({ - network: network, - 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. - * @memberof Miner - * @param {Buffer} a - * @param {Buffer} b - * @returns {Number} - */ - -function rcmp(a, b) { - var i; - - assert(a.length === b.length); - - for (i = a.length - 1; i >= 0; i--) { - if (a[i] < b[i]) - return -1; - if (a[i] > b[i]) - return 1; - } - - return 0; -} - /* * Expose */ diff --git a/lib/workers/framer.js b/lib/workers/framer.js index 601caea6..1e5f18d3 100644 --- a/lib/workers/framer.js +++ b/lib/workers/framer.js @@ -12,13 +12,11 @@ var bn = require('bn.js'); var utils = require('../utils/utils'); var assert = require('assert'); var BufferWriter = require('../utils/writer'); -var Block = require('../primitives/block'); var MTX = require('../primitives/mtx'); var TX = require('../primitives/tx'); var KeyRing = require('../primitives/keyring'); var Script = require('../script/script'); var Witness = require('../script/witness'); -var MinerBlock = require('../miner/minerblock'); /** * Framer @@ -66,11 +64,16 @@ Framer.item = function _item(item, writer) { p.writeVarString(item, 'utf8'); break; case 'number': - p.writeU8(2); - p.write32(item); + if (item > 0x7fffffff) { + p.writeU8(3); + p.writeU32(item); + } else { + p.writeU8(2); + p.write32(item); + } break; case 'boolean': - p.writeU8(3); + p.writeU8(4); p.writeU8(item ? 1 : 0); break; case 'object': @@ -91,29 +94,23 @@ Framer.item = function _item(item, writer) { } else if (item instanceof TX) { p.writeU8(43); item.toExtended(true, p); - } else if (item instanceof Block) { - p.writeU8(44); - item.toRaw(p); - } else if (item instanceof MinerBlock) { - p.writeU8(45); - item.toRaw(p); } else if (item instanceof KeyRing) { - p.writeU8(46); + p.writeU8(44); item.toRaw(p); } else if (bn.isBN(item)) { p.writeU8(10); p.writeVarBytes(item.toArrayLike(Buffer)); } else if (Buffer.isBuffer(item)) { - p.writeU8(4); + p.writeU8(5); p.writeVarBytes(item); } else if (Array.isArray(item)) { - p.writeU8(5); + p.writeU8(6); p.writeVarint(item.length); for (i = 0; i < item.length; i++) Framer.item(item[i], p); } else { keys = Object.keys(item); - p.writeU8(6); + p.writeU8(7); p.writeVarint(keys.length); for (i = 0; i < keys.length; i++) { p.writeVarString(keys[i], 'utf8'); diff --git a/lib/workers/jobs.js b/lib/workers/jobs.js index 8d14eaf6..4f168361 100644 --- a/lib/workers/jobs.js +++ b/lib/workers/jobs.js @@ -8,6 +8,7 @@ var ec = require('../crypto/ec'); var scrypt = require('../crypto/scrypt'); +var mine = require('../miner/mine'); /** * Jobs to execute within the worker. @@ -116,17 +117,15 @@ jobs.ecSign = function ecSign(msg, key) { /** * Mine a block on worker. - * @param {Object} attempt - Naked {@link MinerBlock}. - * @returns {Block} + * @param {Buffer} data + * @param {Buffer} target + * @param {Number} min + * @param {Number} max + * @returns {Number} */ -jobs.mine = function mine(attempt) { - if (jobs.master) { - attempt.on('status', function(status) { - jobs.master.sendEvent('status', status); - }); - } - return attempt.mineSync(); +jobs.mine = function _mine(data, target, min, max) { + return mine(data, target, min, max); }; /** @@ -142,5 +141,5 @@ jobs.mine = function mine(attempt) { */ jobs.scrypt = function _scrypt(passwd, salt, N, r, p, len) { - return scrypt(passwd, salt, N >>> 0, r >>> 0, p >>> 0, len); + return scrypt(passwd, salt, N, r, p, len); }; diff --git a/lib/workers/parser.js b/lib/workers/parser.js index d8515d94..9f449e1d 100644 --- a/lib/workers/parser.js +++ b/lib/workers/parser.js @@ -12,13 +12,11 @@ var bn = require('bn.js'); var utils = require('../utils/utils'); var assert = require('assert'); var BufferReader = require('../utils/reader'); -var Block = require('../primitives/block'); var MTX = require('../primitives/mtx'); var TX = require('../primitives/tx'); var KeyRing = require('../primitives/keyring'); var Script = require('../script/script'); var Witness = require('../script/witness'); -var MinerBlock = require('../miner/minerblock'); /** * Parser @@ -164,16 +162,18 @@ Parser.parseItem = function parseItem(data) { case 2: return p.read32(); case 3: - return p.readU8() === 1; + return p.readU32(); case 4: - return p.readVarBytes(); + return p.readU8() === 1; case 5: + return p.readVarBytes(); + case 6: items = []; count = p.readVarint(); for (i = 0; i < count; i++) items.push(Parser.parseItem(p)); return items; - case 6: + case 7: items = {}; count = p.readVarint(); for (i = 0; i < count; i++) @@ -190,10 +190,6 @@ Parser.parseItem = function parseItem(data) { case 43: return TX.fromExtended(p, true); case 44: - return Block.fromRaw(p); - case 45: - return MinerBlock.fromRaw(p); - case 46: return KeyRing.fromRaw(p); default: throw new Error('Bad type.'); diff --git a/lib/workers/workers.js b/lib/workers/workers.js index 3c51bab2..c9282425 100644 --- a/lib/workers/workers.js +++ b/lib/workers/workers.js @@ -44,6 +44,7 @@ function Workers(options) { this.timeout = options.timeout || 60000; this.children = []; this.nonce = 0; + this.enabled = true; } utils.inherits(Workers, EventEmitter); @@ -220,7 +221,7 @@ Workers.prototype.destroy = function destroy() { Workers.prototype.execute = function execute(method, args, timeout) { var result, child; - if (!Workers.enabled) { + if (!this.enabled || !Workers.support) { return new Promise(function(resolve, reject) { utils.nextTick(function() { try { @@ -338,12 +339,15 @@ Workers.prototype.ecSign = function ecSign(msg, key) { /** * Execute the mining job (no timeout). - * @param {MinerBlock} attempt - * @returns {Promise} - Returns {@link MinerBlock}. + * @param {Buffer} data + * @param {Buffer} target + * @param {Number} min + * @param {Number} max + * @returns {Promise} - Returns {Number}. */ -Workers.prototype.mine = function mine(attempt) { - return this.execute('mine', [attempt], -1); +Workers.prototype.mine = function mine(data, target, min, max) { + return this.execute('mine', [data, target, min, max], -1); }; /** @@ -861,22 +865,24 @@ function fromError(err) { */ Workers.pool = new Workers(); -Workers.enabled = false; +Workers.pool.enabled = false; + +Workers.support = true; + +if (utils.isBrowser) { + Workers.support = typeof global.Worker === 'function' + || typeof global.postMessage === 'function'; +} Workers.set = function set(options) { if (typeof options.useWorkers === 'boolean') - this.enabled = options.useWorkers; + this.pool.enabled = options.useWorkers; if (utils.isNumber(options.maxWorkers)) this.pool.size = options.maxWorkers; if (utils.isNumber(options.workerTimeout)) this.pool.timeout = options.workerTimeout; - - if (utils.isBrowser && this.enabled) { - this.enabled = typeof global.Worker === 'function' - || typeof global.postMessage === 'function'; - } }; Workers.set({ diff --git a/test/chain-test.js b/test/chain-test.js index 440039ac..fd1dbe59 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -27,7 +27,7 @@ describe('Chain', function() { var redeemer; if (!tx) - return attempt.mineSync(); + return yield attempt.mineAsync(); redeemer = bcoin.mtx(); @@ -49,7 +49,7 @@ describe('Chain', function() { attempt.addTX(redeemer.toTX()); - return attempt.mineSync(); + return yield attempt.mineAsync(); }); function deleteCoins(tx) {