/*! * miner.js - inefficient miner 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 */ 'use strict'; var utils = require('../utils/utils'); var co = require('../utils/co'); var crypto = require('../crypto/crypto'); var assert = require('assert'); 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'); /** * MinerBlock * @exports MinerBlock * @constructor * @param {Object} options * @param {ChainEntry} options.tip * @param {Number} options.height * @param {Number} options.target - Compact form. * @param {Base58Address} options.address - Payout address. * @param {Boolean} options.witness - Allow witness * transactions, mine a witness block. * @param {String} options.coinbaseFlags * @property {Block} block * @property {TX} coinbase * @property {BN} hashes - Number of hashes attempted. * @property {Number} rate - Hash rate. * @emits MinerBlock#status */ function MinerBlock(options) { if (!(this instanceof MinerBlock)) return new MinerBlock(options); EventEmitter.call(this); this.options = options; this.workerPool = options.workerPool; this.tip = options.tip; this.height = options.tip.height + 1; this.bits = options.target; this.target = utils.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32); this.extraNonce = new bn(0); this.iterations = 0; this.coinbaseFlags = options.coinbaseFlags; 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.coinbase = new TX(); this.coinbase.mutable = true; this.block = new Block(); this.block.mutable = true; this._init(); } utils.inherits(MinerBlock, EventEmitter); /** * Initialize the block. * @private */ MinerBlock.prototype._init = function _init() { var options = this.options; var block = this.block; var cb = this.coinbase; var i, input, output, hash, witnessNonce; // Coinbase input. input = new Input(); // Height (required in v2+ blocks) input.script.set(0, new bn(this.height)); // extraNonce - incremented when // the nonce overflows. input.script.set(1, this.extraNonce); // Add a nonce to ensure we don't // collide with a previous coinbase // of ours. This isn't really // necessary nowdays due to bip34 // (used above). input.script.set(2, utils.nonce()); // Let the world know this little // miner succeeded. input.script.set(3, this.coinbaseFlags); input.script.compile(); cb.inputs.push(input); // Reward output. output = new Output(); output.script.fromAddress(this.address); cb.outputs.push(output); // If we're using segwit, we need to // set up the nonce and commitment. if (this.witness) { // Our witness nonce is the hash256 // of the previous block hash. hash = new Buffer(this.tip.hash, 'hex'); witnessNonce = crypto.hash256(hash); // Set up the witness nonce. input.witness.set(0, witnessNonce); input.witness.compile(); // Commitment output. cb.outputs.push(new Output()); } // Setup our block. block.version = options.version; block.prevBlock = this.tip.hash; block.merkleRoot = constants.NULL_HASH; block.ts = Math.max(time.now(), this.tip.ts + 1); block.bits = this.bits; block.nonce = 0; block.height = this.height; block.addTX(cb); if (options.txs) { for (i = 0; i < options.txs.length; i++) block.addTX(options.txs[i]); } // Update coinbase since our coinbase was added. this.updateCoinbase(); // Create our merkle root. this.updateMerkle(); }; /** * Update the commitment output for segwit. */ MinerBlock.prototype.updateCommitment = function updateCommitment() { var output = this.coinbase.outputs[1]; var flags = this.coinbaseFlags; var hash; // Recalculate witness merkle root. hash = this.block.getCommitmentHash(); // Update commitment. output.script.clear(); output.script.fromCommitment(hash, flags); }; /** * Update the extra nonce and coinbase reward. */ MinerBlock.prototype.updateCoinbase = function updateCoinbase() { var input = this.coinbase.inputs[0]; var output = this.coinbase.outputs[0]; // Update extra nonce. input.script.set(1, this.extraNonce); input.script.compile(); // Update reward. output.value = this.block.getReward(this.network); }; /** * Increment the extraNonce. */ MinerBlock.prototype.updateNonce = function updateNonce() { this.block.ts = Math.max(time.now(), this.tip.ts + 1); // Overflow the nonce and increment the extraNonce. this.block.nonce = 0; this.extraNonce.iaddn(1); // We incremented the extraNonce, need to update coinbase. this.updateCoinbase(); // We changed the coinbase, need to update merkleRoot. this.updateMerkle(); }; /** * Rebuild the merkle tree and update merkle root as well as the * timestamp (also calls {@link MinerBlock#updateCommitment} * if segwit is enabled). */ MinerBlock.prototype.updateMerkle = function updateMerkle() { // Always update commitment before updating merkle root. // The updated commitment output will change the merkle root. if (this.witness) this.updateCommitment(); // Update timestamp. this.block.ts = Math.max(time.now(), this.tip.ts + 1); // Recalculate merkle root. this.block.merkleRoot = this.block.getMerkleRoot('hex'); }; /** * Add a transaction to the block. Rebuilds the merkle tree, * updates coinbase and commitment. * @param {TX} tx * @returns {Boolean} Whether the transaction was successfully added. */ MinerBlock.prototype.addTX = function addTX(tx) { var weight; assert(!tx.mutable, 'Cannot add mutable TX to block.'); weight = this.block.getWeight() + tx.getWeight(); if (weight > constants.block.MAX_WEIGHT) return false; if (this.block.hasTX(tx)) return false; if (!this.witness && tx.hasWitness()) return false; // Add the tx to our block this.block.addTX(tx); // Update coinbase value this.updateCoinbase(); // Update merkle root for new coinbase and new tx this.updateMerkle(); return true; }; /** * Hash until the nonce overflows, increment extraNonce, rebuild merkletree. * @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; // Track how long we've been at it. this.begin = utils.now(); assert(block.ts > 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; } // Increment the nonce to get a different hash block.nonce++; // 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(); } // Keep track of our iterations. this.iterations++; // Send progress report. this.sendStatus(); // If we took more a second or more (likely), // skip incrementing the extra nonce and just // 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; } // 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`). */ MinerBlock.prototype.sendStatus = function sendStatus() { this.emit('status', { block: this.block, target: this.block.bits, hashes: this.hashes, hashrate: this.rate, height: this.height, best: utils.revHex(this.tip.hash) }); }; /** * 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. */ MinerBlock.prototype.destroy = function destroy() { if (this.timeout) { clearTimeout(this.timeout); this.timeout = null; } this.block = null; }; /** * 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 */ module.exports = MinerBlock;