From 945fa381c5b117ef14751d945f64acce6a16e4ae Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 10 Mar 2017 03:57:44 -0800 Subject: [PATCH] refactor: mining. --- lib/http/index.js | 1 + lib/http/rpc.js | 114 ++++--- lib/mining/cpuminer.js | 562 ++++++++++++++++++++++++++++++ lib/mining/index.js | 3 +- lib/mining/miner.js | 293 ++++------------ lib/mining/minerblock.js | 719 --------------------------------------- lib/mining/template.js | 621 +++++++++++++++++++++++++++++++++ lib/utils/index.js | 1 + lib/wallet/rpc.js | 1 - test/chain-test.js | 278 ++++++++------- test/node-test.js | 46 +-- 11 files changed, 1492 insertions(+), 1147 deletions(-) create mode 100644 lib/mining/cpuminer.js delete mode 100644 lib/mining/minerblock.js create mode 100644 lib/mining/template.js diff --git a/lib/http/index.js b/lib/http/index.js index f5d23c9e..ae1e0899 100644 --- a/lib/http/index.js +++ b/lib/http/index.js @@ -17,4 +17,5 @@ exports.RPCClient = require('./rpcclient'); exports.Wallet = require('./wallet'); exports.Base = require('./base'); exports.RPC = require('./rpc'); +exports.RPCBase = require('./rpcbase'); exports.Server = require('./server'); diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 0030ebef..3a9bb027 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -20,7 +20,6 @@ var Block = require('../primitives/block'); var Headers = require('../primitives/headers'); var Input = require('../primitives/input'); var KeyRing = require('../primitives/keyring'); -var Lock = require('../utils/lock'); var MerkleBlock = require('../primitives/merkleblock'); var MTX = require('../primitives/mtx'); var Network = require('../protocol/network'); @@ -64,7 +63,9 @@ function RPC(node) { this.attempt = null; this.lastActivity = 0; this.boundChain = false; - this.nonces = {}; + this.nonce1 = 0; + this.nonce2 = 0; + this.jobs = {}; this.pollers = []; this.init(); @@ -920,38 +921,43 @@ RPC.prototype.submitWork = co(function* submitWork(data) { RPC.prototype._submitWork = co(function* _submitWork(data) { var attempt = this.attempt; - var block, entry, header, nonce, ts, nonces; + var header = Headers.fromAbbr(data); + var nonce = header.nonce; + var ts = header.ts; + var job, n1, n2, hash, block, entry; if (!attempt) return false; - block = attempt.block; - header = Headers.fromAbbr(data); - nonce = header.nonce; - ts = header.ts; - if (data.length !== 128) throw new RPCError('Invalid work size.'); data = data.slice(0, 80); data = swap32(data); - if (header.prevBlock !== block.prevBlock - || header.bits !== block.bits) { + if (header.prevBlock !== attempt.prevBlock + || header.bits !== attempt.bits) { return false; } if (!header.verify()) return false; - nonces = this.nonces[header.merkleRoot]; + job = this.jobs[header.merkleRoot]; - if (!nonces) + if (!job) return false; - if (!nonces.verify(attempt, nonce, ts)) + n1 = job.nonce1; + n2 = job.nonce2; + + hash = attempt.hash(n1, n2, ts, nonce); + + if (!consensus.verifyPOW(hash, attempt.bits)) return false; + block = attempt.commit(n1, n2, ts, nonce); + try { entry = yield this.chain.add(block); } catch (err) { @@ -982,14 +988,17 @@ RPC.prototype.createWork = co(function* createWork(data) { }); RPC.prototype._createWork = co(function* _createWork() { - var attempt = yield this.updateAttempt(); - var data, abbr; + var attempt = yield this.updateWork(); + var n1 = this.nonce1; + var n2 = this.nonce2; + var ts = attempt.ts; + var data, head; data = new Buffer(128); data.fill(0); - abbr = attempt.block.abbr(); - abbr.copy(data, 0); + head = attempt.getHeader(n1, n2, ts, 0); + head.copy(data, 0); data[80] = 0x80; data.writeUInt32BE(80 * 8, data.length - 4, true); @@ -1162,9 +1171,8 @@ RPC.prototype.createTemplate = co(function* createTemplate(version, coinbase, ru }); RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, rules) { - var attempt = yield this.getAttempt(); + var attempt = yield this.getTemplate(); var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR; - var block = attempt.block; var mutable = ['time', 'transactions', 'prevblock']; var txs = []; var index = {}; @@ -1217,11 +1225,11 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, case common.thresholdStates.FAILED: break; case common.thresholdStates.LOCKED_IN: - block.version |= 1 << deploy.bit; + attempt.version |= 1 << deploy.bit; case common.thresholdStates.STARTED: if (!deploy.force) { if (rules.indexOf(name) === -1) - block.version &= ~(1 << deploy.bit); + attempt.version &= ~(1 << deploy.bit); name = '!' + name; } vbavailable[name] = deploy.bit; @@ -1240,24 +1248,24 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, } } - block.version >>>= 0; + attempt.version >>>= 0; json = { capabilities: ['proposal'], mutable: mutable, - version: block.version, + version: attempt.version, rules: vbrules, vbavailable: vbavailable, vbrequired: 0, height: attempt.height, - previousblockhash: util.revHex(block.prevBlock), + previousblockhash: util.revHex(attempt.prevBlock), target: util.revHex(attempt.target.toString('hex')), - bits: util.hex32(block.bits), + bits: util.hex32(attempt.bits), noncerange: '00000000ffffffff', - curtime: block.ts, - mintime: block.ts, - maxtime: block.ts + 7200, - expires: block.ts + 7200, + curtime: attempt.ts, + mintime: attempt.ts, + maxtime: attempt.ts + 7200, + expires: attempt.ts + 7200, sigoplimit: consensus.MAX_BLOCK_SIGOPS_COST / scale | 0, sizelimit: consensus.MAX_BLOCK_SIZE, weightlimit: undefined, @@ -1266,7 +1274,7 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, coinbaseaux: { flags: attempt.coinbaseFlags.toString('hex') }, - coinbasevalue: attempt.coinbase.getOutputValue(), + coinbasevalue: attempt.getReward(), coinbasetxn: undefined, default_witness_commitment: undefined, transactions: txs @@ -1280,13 +1288,14 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, } if (coinbase) { - tx = attempt.coinbase; + tx = attempt.toCoinbase(); // We don't include the commitment // output (see bip145). if (attempt.witness) { output = tx.outputs.pop(); assert(output.script.isCommitment()); + tx.refresh(); } json.coinbasetxn = { @@ -1298,13 +1307,10 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, sigops: tx.getSigopsCost() / scale | 0, weight: tx.getWeight() }; - - if (attempt.witness) - tx.outputs.push(output); } if (attempt.witness) { - tx = attempt.coinbase; + tx = attempt.toCoinbase(); output = tx.outputs[tx.outputs.length - 1]; assert(output.script.isCommitment()); json.default_witness_commitment = output.script.toJSON(); @@ -1315,21 +1321,19 @@ RPC.prototype._createTemplate = co(function* _createTemplate(version, coinbase, RPC.prototype.getMiningInfo = co(function* getMiningInfo(args, help) { var attempt = this.attempt; - var hashps; + var scale = attempt.witness ? 1 : consensus.WITNESS_SCALE_FACTOR; if (help || args.length !== 0) throw new RPCError('getmininginfo'); - hashps = yield this.getHashRate(120); - return { blocks: this.chain.height, - currentblocksize: attempt ? attempt.block.getBaseSize() : 0, - currentblocktx: attempt ? attempt.block.txs.length : 0, + currentblocksize: attempt ? attempt.weight / scale | 0 : 0, + currentblocktx: attempt ? attempt.items.length + 1 : 0, difficulty: this.difficulty(), errors: '', genproclimit: this.procLimit, - networkhashps: hashps, + networkhashps: yield this.getHashRate(120), pooledtx: this.totalTX(), testnet: this.network !== Network.main, chain: 'main', @@ -2113,7 +2117,9 @@ RPC.prototype.refreshBlock = function refreshBlock() { this.attempt = null; this.lastActivity = 0; - this.coinbase = {}; + this.jobs = {}; + this.nonce1 = 0; + this.nonce2 = 0; this.pollers = []; for (i = 0; i < pollers.length; i++) { @@ -2149,7 +2155,7 @@ RPC.prototype.bindChain = function bindChain() { }); }; -RPC.prototype.getAttempt = co(function* getAttempt() { +RPC.prototype.getTemplate = co(function* getTemplate() { var attempt = this.attempt; this.bindChain(); @@ -2163,22 +2169,31 @@ RPC.prototype.getAttempt = co(function* getAttempt() { return attempt; }); -RPC.prototype.updateAttempt = co(function* updateAttempt() { +RPC.prototype.updateWork = co(function* updateWork() { var attempt = this.attempt; + var root; this.bindChain(); if (attempt) { - attempt.updateNonce(); - this.nonces[block.merkleRoot] = attempt.snapshot(); + if (++this.nonce1 === 0xffffffff) { + this.nonce1 = 0; + this.nonce2++; + } + root = attempt.getRoot(this.nonce1, this.nonce2); + root = root.toString('hex'); + this.jobs[root] = new Nonces(this); return attempt; } attempt = yield this.miner.createBlock(); + root = attempt.getRoot(this.nonce1, this.nonce2); + root = root.toString('hex'); + this.attempt = attempt; this.lastActivity = util.now(); - this.nonces[block.merkleRoot] = attempt.snapshot(); + this.jobs[root] = new Nonces(this); return attempt; }); @@ -2530,6 +2545,11 @@ function toDeployment(id, version, status) { }; } +function Nonces(rpc) { + this.nonce1 = rpc.nonce1; + this.nonce2 = rpc.nonce2; +} + /* * Expose */ diff --git a/lib/mining/cpuminer.js b/lib/mining/cpuminer.js new file mode 100644 index 00000000..7f5fecce --- /dev/null +++ b/lib/mining/cpuminer.js @@ -0,0 +1,562 @@ +/*! + * cpuminer.js - inefficient cpu miner for bcoin (because we can) + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var assert = require('assert'); +var util = require('../utils/util'); +var co = require('../utils/co'); +var AsyncObject = require('../utils/asyncobject'); +var workerPool = require('../workers/workerpool').pool; +var mine = require('./mine'); + +/** + * CPU miner. + * @alias module:mining.CPUMiner + * @constructor + * @param {Miner} miner + * @emits CPUMiner#block + * @emits CPUMiner#status + */ + +function CPUMiner(miner) { + if (!(this instanceof CPUMiner)) + return new CPUMiner(miner); + + AsyncObject.call(this); + + this.miner = miner; + this.network = this.miner.network; + this.logger = this.miner.logger; + this.chain = this.miner.chain; + + this.running = false; + this.stopping = false; + this.job = null; + this.since = 0; + + this._init(); +} + +util.inherits(CPUMiner, AsyncObject); + +/** + * Nonce range interval. + * @const {Number} + * @default + */ + +CPUMiner.INTERVAL = 0xffffffff / 1500 | 0; + +/** + * Initialize the miner. + * @private + */ + +CPUMiner.prototype._init = function _init() { + var self = this; + + this.chain.on('tip', function(tip) { + if (!self.job) + return; + + if (self.job.attempt.prevBlock === tip.prevBlock) + self.job.destroy(); + }); + + this.on('block', function(block, entry) { + // Emit the block hex as a failsafe (in case we can't send it) + self.logger.info('Found block: %d (%s).', entry.height, entry.rhash()); + self.logger.debug('Raw: %s', block.toRaw().toString('hex')); + }); + + this.on('status', function(stat) { + // Send progress report. + self.logger.info( + 'CPUMiner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s', + stat.hashrate / 1000 | 0, + stat.hashes, + stat.target, + stat.height, + stat.best); + }); +}; + +/** + * Open the miner. + * @method + * @alias module:mining.CPUMiner#open + * @returns {Promise} + */ + +CPUMiner.prototype._open = co(function* open() { +}); + +/** + * Close the miner. + * @method + * @alias module:mining.CPUMiner#close + * @returns {Promise} + */ + +CPUMiner.prototype._close = co(function* close() { + if (!this.running) + return; + + if (this.stopping) { + yield this._onStop(); + return; + } + + yield this.stop(); +}); + +/** + * Start mining. + * @method + * @returns {Promise} + */ + +CPUMiner.prototype.start = co(function* start() { + var block, entry; + + assert(!this.running, 'CPUMiner is already running.'); + + this.running = true; + this.stopping = false; + + for (;;) { + this.job = null; + + try { + this.job = yield this.createJob(); + } catch (e) { + if (this.stopping) + break; + this.emit('error', e); + continue; + } + + if (this.stopping) + break; + + try { + block = yield this.mineAsync(this.job); + } catch (e) { + if (this.stopping) + break; + this.emit('error', e); + continue; + } + + if (this.stopping) + break; + + if (!block) + continue; + + try { + entry = yield this.chain.add(block); + } catch (e) { + if (this.stopping) + break; + this.emit('error', e); + continue; + } + + if (!entry) { + this.logger.warning('Mined a bad-prevblk (race condition?)'); + continue; + } + + if (this.stopping) + break; + + this.emit('block', block, entry); + } + + this.emit('done'); +}); + +/** + * Stop mining. + * @method + * @returns {Promise} + */ + +CPUMiner.prototype.stop = co(function* stop() { + assert(this.running, 'CPUMiner is not running.'); + assert(!this.stopping, 'CPUMiner is already stopping.'); + + this.stopping = true; + + yield this._onDone(); + + this.running = false; + this.stopping = false; + this.job = null; + + this.emit('stop'); +}); + +/** + * Wait for `done` event. + * @private + * @returns {Promise} + */ + +CPUMiner.prototype._onDone = function _onDone() { + var self = this; + return new Promise(function(resolve, reject) { + self.once('done', resolve); + }); +}; + +/** + * Wait for `stop` event. + * @private + * @returns {Promise} + */ + +CPUMiner.prototype._onStop = function _onStop() { + var self = this; + return new Promise(function(resolve, reject) { + self.once('stop', resolve); + }); +}; + +/** + * Create a mining job. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link Job}. + */ + +CPUMiner.prototype.createJob = co(function* createJob(tip, address) { + var attempt = yield this.miner.createBlock(tip, address); + return new CPUJob(this, attempt); +}); + +/** + * Mine a single block. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns [{@link Block}]. + */ + +CPUMiner.prototype.mineBlock = co(function* mineBlock(tip, address) { + var job = yield this.createJob(tip, address); + return yield this.mineAsync(job); +}); + +/** + * Notify the miner that a new + * tx has entered the mempool. + */ + +CPUMiner.prototype.notifyEntry = function notifyEntry() { + if (!this.running) + return; + + if (!this.job) + return; + + if (++this.since > 20) { + this.since = 0; + this.job.destroy(); + } +}; + +/** + * Hash until the nonce overflows. + * @param {CPUJob} job + * @returns {Number} nonce + */ + +CPUMiner.prototype.findNonce = function findNonce(job) { + var data = job.getHeader(0); + var target = job.attempt.target; + var interval = CPUMiner.INTERVAL; + var min = 0; + var max = interval; + var nonce; + + while (max <= 0xffffffff) { + nonce = mine(data, target, min, max); + + if (nonce !== -1) + break; + + this.sendStatus(job, max); + + min += interval; + max += interval; + } + + return nonce; +}; + +/** + * Hash until the nonce overflows. + * @method + * @param {CPUJob} job + * @returns {Promise} Returns Number. + */ + +CPUMiner.prototype.findNonceAsync = co(function* findNonceAsync(job) { + var data = job.getHeader(0); + var target = job.attempt.target; + var interval = CPUMiner.INTERVAL; + var min = 0; + var max = interval; + var nonce; + + while (max <= 0xffffffff) { + nonce = yield workerPool.mine(data, target, min, max); + + if (nonce !== -1) + break; + + if (job.destroyed) + return nonce; + + this.sendStatus(job, max); + + min += interval; + max += interval; + } + + return nonce; +}); + +/** + * Mine synchronously until the block is found. + * @param {CPUJob} job + * @returns {Block} + */ + +CPUMiner.prototype.mine = function mine(job) { + var nonce; + + // Track how long we've been at it. + job.begin = util.now(); + + for (;;) { + nonce = this.findNonce(job); + + if (nonce !== -1) + break; + + this.iterate(job); + } + + return job.commit(nonce); +}; + +/** + * Mine asynchronously until the block is found. + * @method + * @param {CPUJob} job + * @returns {Promise} - Returns {@link Block}. + */ + +CPUMiner.prototype.mineAsync = co(function* mineAsync(job) { + var nonce; + + // Track how long we've been at it. + job.begin = util.now(); + + for (;;) { + nonce = yield this.findNonceAsync(job); + + if (nonce !== -1) + break; + + if (job.destroyed) + return; + + this.iterate(job); + } + + return job.commit(nonce); +}); + +/** + * Increment extraNonce and send status. + * @param {CPUJob} job + */ + +CPUMiner.prototype.iterate = function iterate(job) { + job.iterations++; + job.updateNonce(); + this.sendStatus(job, 0); +}; + +/** + * Send a progress report (emits `status`). + * @param {CPUJob} job + * @param {Number} nonce + */ + +CPUMiner.prototype.sendStatus = function sendStatus(job, nonce) { + this.emit('status', { + target: job.attempt.bits, + hashes: job.getHashes(), + hashrate: job.getRate(nonce), + height: job.attempt.height, + best: util.revHex(job.attempt.prevBlock) + }); +}; + +/** + * Mining Job + * @constructor + * @ignore + * @param {CPUMiner} miner + * @param {BlockTemplate} attempt + */ + +function CPUJob(miner, attempt) { + this.miner = miner; + this.attempt = attempt; + this.destroyed = false; + this.committed = false; + this.iterations = 0; + this.begin = 0; + this.nonce1 = 0; + this.nonce2 = 0; + this.refresh(); +} + +/** + * Get the raw block header. + * @param {Number} nonce + * @returns {Buffer} + */ + +CPUJob.prototype.getHeader = function getHeader(nonce) { + var attempt = this.attempt; + var n1 = this.nonce1; + var n2 = this.nonce2; + var ts = attempt.ts; + return this.attempt.getHeader(n1, n2, ts, nonce); +}; + +/** + * Commit job and return a block. + * @param {Number} nonce + * @returns {Block} + */ + +CPUJob.prototype.commit = function commit(nonce) { + var attempt = this.attempt; + var n1 = this.nonce1; + var n2 = this.nonce2; + var ts = attempt.ts; + + assert(!this.committed, 'Job already committed.'); + this.committed = true; + + return this.attempt.commit(n1, n2, ts, nonce); +}; + +/** + * Mine block synchronously. + * @returns {Block} + */ + +CPUJob.prototype.mine = function mine() { + return this.miner.mine(this); +}; + +/** + * Mine block asynchronously. + * @returns {Promise} + */ + +CPUJob.prototype.mineAsync = function mineAsync() { + return this.miner.mineAsync(this); +}; + +/** + * Refresh the block template. + */ + +CPUJob.prototype.refresh = function refresh() { + return this.attempt.refresh(); +}; + +/** + * Increment the extraNonce. + */ + +CPUJob.prototype.updateNonce = function() { + // Overflow the nonce and increment the extraNonce. + this.nonce1++; + + // Wrap at 4 bytes. + if (this.nonce1 === 0xffffffff) { + this.nonce1 = 0; + this.nonce2++; + } +}; + +/** + * Destroy the job. + */ + +CPUJob.prototype.destroy = function() { + assert(!this.destroyed, 'Job already destroyed.'); + this.destroyed = true; +}; + +/** + * Calculate number of hashes. + * @returns {Number} + */ + +CPUJob.prototype.getHashes = function() { + return this.iterations * 0xffffffff + this.block.nonce; +}; + +/** + * Calculate hashrate. + * @returns {Number} + */ + +CPUJob.prototype.getRate = function(nonce) { + return (nonce / (util.now() - this.begin)) | 0; +}; + +/** + * Add a transaction to the block. + * @param {TX} tx + * @param {CoinView} view + */ + +CPUJob.prototype.addTX = function(tx, view) { + return this.attempt.addTX(tx, view); +}; + +/** + * Add a transaction to the block + * (less verification than addTX). + * @param {TX} tx + * @param {CoinView?} view + */ + +CPUJob.prototype.pushTX = function(tx, view) { + return this.attempt.pushTX(tx, view); +}; + +/* + * Expose + */ + +module.exports = CPUMiner; diff --git a/lib/mining/index.js b/lib/mining/index.js index ebc7d99d..6e5ddd59 100644 --- a/lib/mining/index.js +++ b/lib/mining/index.js @@ -4,6 +4,7 @@ * @module mining */ +exports.BlockTemplate = require('./template'); +exports.CPUMiner = require('./cpuminer'); exports.mine = require('./mine'); exports.Miner = require('./miner'); -exports.MinerBlock = require('./minerblock'); diff --git a/lib/mining/miner.js b/lib/mining/miner.js index 9a91942e..9a666701 100644 --- a/lib/mining/miner.js +++ b/lib/mining/miner.js @@ -1,5 +1,5 @@ /*! - * miner.js - inefficient miner for bcoin (because we can) + * miner.js - block generator for bcoin * Copyright (c) 2014-2015, Fedor Indutny (MIT License) * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bcoin @@ -14,23 +14,18 @@ var Heap = require('../utils/heap'); var AsyncObject = require('../utils/asyncobject'); var Amount = require('../btc/amount'); var Address = require('../primitives/address'); -var MinerBlock = require('./minerblock'); +var BlockTemplate = require('./template'); var Network = require('../protocol/network'); var consensus = require('../protocol/consensus'); var policy = require('../protocol/policy'); -var BlockEntry = MinerBlock.BlockEntry; +var CPUMiner = require('./cpuminer'); +var BlockEntry = BlockTemplate.BlockEntry; /** - * A bitcoin miner (supports mining witness blocks). + * A bitcoin miner and block generator. * @alias module:mining.Miner * @constructor * @param {Object} options - * @param {Address} options.address - Payout address. - * @param {String} [options.coinbaseFlags="mined by bcoin"] - * @property {Boolean} running - * @property {MinerBlock} attempt - * @emits Miner#block - * @emits Miner#status */ function Miner(options) { @@ -40,58 +35,17 @@ function Miner(options) { AsyncObject.call(this); this.options = new MinerOptions(options); - this.network = this.options.network; this.logger = this.options.logger; this.chain = this.options.chain; this.mempool = this.options.mempool; this.addresses = this.options.addresses; - this.locker = this.chain.locker; - - this.running = false; - this.stopping = false; - this.attempt = null; - this.since = 0; - - this._init(); + this.cpu = new CPUMiner(this); } util.inherits(Miner, AsyncObject); -/** - * Initialize the miner. - * @private - */ - -Miner.prototype._init = function _init() { - var self = this; - - this.chain.on('tip', function(tip) { - if (!self.attempt) - return; - - if (self.attempt.block.prevBlock === tip.prevBlock) - self.attempt.destroy(); - }); - - this.on('block', function(block, entry) { - // Emit the block hex as a failsafe (in case we can't send it) - self.logger.info('Found block: %d (%s).', entry.height, entry.rhash()); - self.logger.debug('Raw: %s', block.toRaw().toString('hex')); - }); - - this.on('status', function(stat) { - self.logger.info( - 'Miner: hashrate=%dkhs hashes=%d target=%d height=%d best=%s', - stat.hashrate / 1000 | 0, - stat.hashes, - stat.target, - stat.height, - stat.best); - }); -}; - /** * Open the miner, wait for the chain and mempool to load. * @method @@ -105,6 +59,8 @@ Miner.prototype._open = co(function* open() { if (this.mempool) yield this.mempool.open(); + yield this.cpu.open(); + this.logger.info('Miner loaded (flags=%s).', this.options.coinbaseFlags.toString('utf8')); @@ -120,145 +76,15 @@ Miner.prototype._open = co(function* open() { */ Miner.prototype._close = co(function* close() { - if (!this.running) - return; - - if (this.stopping) { - yield this._onStop(); - return; - } - - yield this.stop(); + yield this.cpu.close(); }); /** - * Start mining. + * Create a block template. * @method - * @param {Number?} version - Custom block version. - * @returns {Promise} - */ - -Miner.prototype.start = co(function* start() { - var self = this; - var block, entry; - - assert(!this.running, 'Miner is already running.'); - - this.running = true; - this.stopping = false; - - for (;;) { - this.attempt = null; - - try { - this.attempt = yield this.createBlock(); - } catch (e) { - if (this.stopping) - break; - this.emit('error', e); - continue; - } - - if (this.stopping) - break; - - this.attempt.on('status', function(status) { - self.emit('status', status); - }); - - try { - block = yield this.attempt.mineAsync(); - } catch (e) { - if (this.stopping) - break; - this.emit('error', e); - continue; - } - - if (this.stopping) - break; - - if (!block) - continue; - - try { - entry = yield this.chain.add(block); - } catch (e) { - if (this.stopping) - break; - this.emit('error', e); - continue; - } - - if (!entry) { - this.logger.warning('Mined a bad-prevblk (race condition?)'); - continue; - } - - if (this.stopping) - break; - - this.emit('block', block, entry); - } - - this.emit('done'); -}); - -/** - * Stop mining. - * @method - * @returns {Promise} - */ - -Miner.prototype.stop = co(function* stop() { - assert(this.running, 'Miner is not running.'); - assert(!this.stopping, 'Miner is already stopping.'); - - this.stopping = true; - - if (this.attempt) - this.attempt.destroy(); - - yield this._onDone(); - - this.running = false; - this.stopping = false; - this.attempt = null; - - this.emit('stop'); -}); - -/** - * Wait for `done` event. - * @private - * @returns {Promise} - */ - -Miner.prototype._onDone = function _onDone() { - var self = this; - return new Promise(function(resolve, reject) { - self.once('done', resolve); - }); -}; - -/** - * Wait for `stop` event. - * @private - * @returns {Promise} - */ - -Miner.prototype._onStop = function _onStop() { - var self = this; - return new Promise(function(resolve, reject) { - self.once('stop', resolve); - }); -}; - -/** - * Create a block "attempt". - * @method - * @param {ChainEntry} tip - * @returns {Promise} - Returns {@link MinerBlock}. + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link BlockTemplate}. */ Miner.prototype.createBlock = co(function* createBlock(tip, address) { @@ -271,16 +97,17 @@ Miner.prototype.createBlock = co(function* createBlock(tip, address) { }); /** - * Create a block "attempt" (without a lock). + * Create a block template (without a lock). * @method * @private - * @param {ChainEntry} tip - * @returns {Promise} - Returns {@link MinerBlock}. + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} - Returns {@link BlockTemplate}. */ Miner.prototype._createBlock = co(function* createBlock(tip, address) { var version = this.options.version; - var ts, locktime, target, attempt; + var ts, mtp, locktime, target, attempt, block; if (!tip) tip = this.chain.tip; @@ -291,19 +118,18 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) { if (version === -1) version = yield this.chain.computeBlockVersion(tip); - if (this.chain.state.hasMTP()) { - locktime = yield tip.getMedianTime(); - ts = Math.max(this.network.now(), locktime + 1); - } else { - ts = Math.max(this.network.now(), tip.ts + 1); - locktime = ts; - } + mtp = yield tip.getMedianTime(); + ts = Math.max(this.network.now(), mtp + 1); + locktime = ts; + + if (this.chain.state.hasMTP()) + locktime = mtp; target = yield this.chain.getTarget(ts, tip); - attempt = new MinerBlock({ - network: this.network, - tip: tip, + attempt = new BlockTemplate({ + prevBlock: tip.hash, + height: tip.height + 1, version: version, ts: ts, bits: target, @@ -312,11 +138,12 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) { address: address, coinbaseFlags: this.options.coinbaseFlags, witness: this.chain.state.hasWitness(), + halvingInterval: this.network.halvingInterval, weight: this.options.reservedWeight, sigops: this.options.reservedSigops }); - this.build(attempt); + this.assemble(attempt); this.logger.debug( 'Created miner block (height=%d, weight=%d, fees=%d, txs=%s).', @@ -326,8 +153,10 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) { attempt.items.length + 1); if (this.options.preverify) { + block = attempt.toBlock(); + try { - yield this.chain._verifyBlock(attempt.block); + yield this.chain._verifyBlock(block); } catch (e) { if (e.type === 'VerifyError') { this.logger.warning('Miner created invalid block!'); @@ -346,34 +175,28 @@ Miner.prototype._createBlock = co(function* createBlock(tip, address) { }); /** - * Mine a single block. + * Create a cpu miner job. * @method - * @param {ChainEntry} tip - * @returns {Promise} - Returns [{@link Block}]. + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} Returns {@link CPUJob}. */ -Miner.prototype.mineBlock = co(function* mineBlock(tip, address) { - var attempt = yield this.createBlock(tip, address); - return yield attempt.mineAsync(); +Miner.prototype.createJob = co(function* createJob(tip, address) { + return yield this.cpu.createJob(tip, address); }); /** - * Notify the miner that a new tx has entered the mempool. - * @param {MempoolEntry} entry + * Mine a single block. + * @method + * @param {ChainEntry?} tip + * @param {Address?} address + * @returns {Promise} Returns {@link Block}. */ -Miner.prototype.notifyEntry = function notifyEntry() { - if (!this.running) - return; - - if (!this.attempt) - return; - - if (++this.since > 20) { - this.since = 0; - this.attempt.destroy(); - } -}; +Miner.prototype.mineBlock = co(function* mineBlock(tip, address) { + return yield this.cpu.mineBlock(tip, address); +}); /** * Add an address to the address list. @@ -391,23 +214,23 @@ Miner.prototype.addAddress = function addAddress(address) { Miner.prototype.getAddress = function getAddress() { if (this.addresses.length === 0) - return; + return new Address(); return this.addresses[Math.random() * this.addresses.length | 0]; }; /** * Get mempool entries, sort by dependency order. * Prioritize by priority and fee rates. + * @param {BlockTemplate} attempt * @returns {MempoolEntry[]} */ -Miner.prototype.build = function build(attempt) { +Miner.prototype.assemble = function assemble(attempt) { var depMap = {}; - var block = attempt.block; var queue = new Heap(cmpRate); var priority = this.options.priorityWeight > 0; var i, j, entry, item, tx, hash, input; - var prev, deps, hashes, weight, sigops; + var prev, deps, hashes, weight, sigops, block; if (priority) queue.set(cmpPriority); @@ -492,8 +315,6 @@ Miner.prototype.build = function build(attempt) { attempt.fees += item.fee; attempt.items.push(item); - block.txs.push(tx); - deps = depMap[hash]; if (!deps) @@ -508,10 +329,18 @@ Miner.prototype.build = function build(attempt) { attempt.refresh(); - assert(block.getWeight() <= attempt.weight, + assert(attempt.weight <= consensus.MAX_BLOCK_WEIGHT, 'Block exceeds reserved weight!'); - assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE, - 'Block exceeds max block size.'); + + if (this.options.preverify) { + block = attempt.toBlock(); + + assert(block.getWeight() <= attempt.weight, + 'Block exceeds reserved weight!'); + + assert(block.getBaseSize() <= consensus.MAX_BLOCK_SIZE, + 'Block exceeds max block size.'); + } }; /** diff --git a/lib/mining/minerblock.js b/lib/mining/minerblock.js deleted file mode 100644 index d7ce7ba9..00000000 --- a/lib/mining/minerblock.js +++ /dev/null @@ -1,719 +0,0 @@ -/*! - * minerblock.js - miner block object for bcoin (because we can) - * Copyright (c) 2014-2015, Fedor Indutny (MIT License) - * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). - * https://github.com/bcoin-org/bcoin - */ - -'use strict'; - -var assert = require('assert'); -var EventEmitter = require('events').EventEmitter; -var BN = require('bn.js'); -var util = require('../utils/util'); -var co = require('../utils/co'); -var StaticWriter = require('../utils/staticwriter'); -var Network = require('../protocol/network'); -var Address = require('../primitives/address'); -var TX = require('../primitives/tx'); -var Block = require('../primitives/block'); -var Input = require('../primitives/input'); -var Output = require('../primitives/output'); -var mine = require('./mine'); -var workerPool = require('../workers/workerpool').pool; -var consensus = require('../protocol/consensus'); -var policy = require('../protocol/policy'); -var encoding = require('../utils/encoding'); - -/** - * MinerBlock (block attempt) - * @alias module:mining.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.network = Network.get(options.network); - this.tip = options.tip; - this.version = options.version; - this.height = options.tip.height + 1; - this.ts = options.ts; - this.bits = options.bits; - this.target = consensus.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32); - this.locktime = options.locktime; - this.flags = options.flags; - this.nonce1 = 0; - this.nonce2 = 0; - this.iterations = 0; - this.coinbaseFlags = options.coinbaseFlags; - this.witness = options.witness; - this.address = options.address; - this.reward = consensus.getReward(this.height, this.network.halvingInterval); - - this.destroyed = false; - this.committed = false; - - this.sigops = options.sigops; - this.weight = options.weight; - this.fees = 0; - this.items = []; - - this.coinbase = new TX(); - this.coinbase.mutable = true; - - this.block = new Block(); - this.block.mutable = true; - - this._init(); -} - -util.inherits(MinerBlock, EventEmitter); - -/** - * Nonce range interval. - * @const {Number} - * @default - */ - -MinerBlock.INTERVAL = 0xffffffff / 1500 | 0; - -/** - * Calculate number of hashes. - * @returns {Number} - */ - -MinerBlock.prototype.getHashes = function() { - return this.iterations * 0xffffffff + this.block.nonce; -}; - -/** - * Calculate hashrate. - * @returns {Number} - */ - -MinerBlock.prototype.getRate = function() { - return (this.block.nonce / (util.now() - this.begin)) | 0; -}; - -/** - * Initialize the block. - * @private - */ - -MinerBlock.prototype._init = function _init() { - var scale = consensus.WITNESS_SCALE_FACTOR; - var hash = encoding.ZERO_HASH; - var block = this.block; - var cb = this.coinbase; - var weight = 0; - var sigops = 0; - var input, output, commit, padding; - - assert(this.coinbaseFlags.length <= 20); - - // Setup our block. - block.version = this.version; - block.prevBlock = this.tip.hash; - block.merkleRoot = encoding.NULL_HASH; - block.ts = this.ts; - block.bits = this.bits; - block.nonce = 0; - - // Coinbase input. - input = new Input(); - - // Height (required in v2+ blocks) - input.script.set(0, new BN(this.height)); - - // Let the world know this little - // miner succeeded. - input.script.set(1, encoding.ZERO_HASH160); - - // Smaller nonce for good measure. - input.script.set(2, util.nonce().slice(0, 4)); - - // extraNonce - incremented when - // the nonce overflows. - input.script.set(3, this.extraNonce()); - - input.script.compile(); - - // Set up the witness nonce. - if (this.witness) { - input.witness.set(0, block.createWitnessNonce()); - input.witness.compile(); - } - - cb.inputs.push(input); - - // Reward output. - output = new Output(); - output.script.fromPubkeyhash(encoding.ZERO_HASH160); - - cb.outputs.push(output); - - // If we're using segwit, we - // need to set up the commitment. - if (this.witness) { - // Commitment output. - commit = new Output(); - commit.script.fromCommitment(hash); - cb.outputs.push(commit); - } - - block.txs.push(cb); - - // Initialize weight. - weight = block.getWeight(); - - // 4 extra bytes for varint tx count. - weight += 4 * scale; - - // Padding for the CB height (constant size). - padding = 5 - input.script.code[0].getSize(); - assert(padding >= 0); - - weight += padding * scale; - - // Reserved size. - // Without segwit: - // Block weight = 840 - // Block stripped size = 210 - // Block size = 210 - // CB weight = 500 - // CB stripped size = 125 - // CB size = 125 - // Sigops cost = 4 - // With segwit: - // Block weight = 1064 - // Block stripped size = 257 - // Block size = 293 - // CB weight = 724 - // CB stripped size = 172 - // CB size = 208 - // Sigops cost = 4 - if (!this.witness) { - assert.equal(weight, 840); - assert.equal(block.getBaseSize() + 4 + padding, 210); - assert.equal(block.getSize() + 4 + padding, 210); - assert.equal(cb.getWeight() + padding * scale, 500); - assert.equal(cb.getBaseSize() + padding, 125); - assert.equal(cb.getSize() + padding, 125); - } else { - assert.equal(weight, 1064); - assert.equal(block.getBaseSize() + 4 + padding, 257); - assert.equal(block.getSize() + 4 + padding, 293); - assert.equal(cb.getWeight() + padding * scale, 724); - assert.equal(cb.getBaseSize() + padding, 172); - assert.equal(cb.getSize() + padding, 208); - } - - // Initialize sigops weight. - sigops = 4; - - // Setup coinbase flags (variable size). - input.script.set(1, this.coinbaseFlags); - input.script.compile(); - - // Setup output script (variable size). - if (this.address) { - output.script.clear(); - output.script.fromAddress(this.address); - } - - // Update commitments. - this.refresh(); - - // Ensure the variable size - // stuff didn't break anything. - assert(block.getWeight() <= weight, - 'Coinbase exceeds reserved size!'); - - assert(cb.getSigopsCost(null, this.flags) <= sigops, - 'Coinbase exceeds reserved sigops!'); - - assert(this.weight >= weight, - 'Coinbase exceeds reserved size!'); - - assert(this.sigops >= sigops, - 'Coinbase exceeds reserved sigops!'); -}; - -/** - * Update coinbase, witness - * commitment, and merkle root. - */ - -MinerBlock.prototype.refresh = function refresh() { - // Update coinbase. - this.updateCoinbase(); - - // Witness commitment. - if (this.witness) - this.updateCommitment(); - - // Create our merkle root. - this.updateMerkle(); -}; - -/** - * Update the commitment output for segwit. - */ - -MinerBlock.prototype.updateCommitment = function updateCommitment() { - var output = this.coinbase.outputs[1]; - var hash; - - // Recalculate witness merkle root. - hash = this.block.createCommitmentHash(); - - // Update commitment. - output.script.clear(); - output.script.fromCommitment(hash); -}; - -/** - * 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(3, this.extraNonce()); - input.script.compile(); - - // Update reward. - output.value = this.reward + this.fees; -}; - -/** - * Increment the extraNonce. - */ - -MinerBlock.prototype.updateNonce = function updateNonce() { - // Overflow the nonce and increment the extraNonce. - this.block.nonce = 0; - this.nonce1++; - - // Wrap at 4 bytes. - if (this.nonce1 === 0xffffffff) { - this.nonce1 = 0; - this.nonce2++; - } - - // 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. - */ - -MinerBlock.prototype.updateMerkle = function updateMerkle() { - // Recalculate merkle root. - this.block.merkleRoot = this.block.createMerkleRoot('hex'); -}; - -/** - * Render extraNonce. - * @returns {Buffer} - */ - -MinerBlock.prototype.extraNonce = function extraNonce() { - var bw = new StaticWriter(8); - bw.writeU32BE(this.nonce1); - bw.writeU32BE(this.nonce2); - return bw.render(); -}; - -/** - * Set the reward output address. - * @param {Address} address - */ - -MinerBlock.prototype.setAddress = function setAddress(address) { - var output = this.coinbase.outputs[0]; - - this.address = Address(address); - - output.script.clear(); - output.script.fromAddress(this.address); - - // Update commitments. - this.refresh(); -}; - -/** - * 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, view) { - var hash = tx.hash('hex'); - var item, weight, sigops; - - assert(!tx.mutable, 'Cannot add mutable TX to block.'); - - if (this.block.hasTX(hash)) - return false; - - item = BlockEntry.fromTX(tx, view, this); - weight = item.tx.getWeight(); - sigops = item.sigops; - - if (!tx.isFinal(this.height, this.locktime)) - return false; - - if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) - return false; - - if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) - return false; - - if (!this.witness && tx.hasWitness()) - return false; - - this.weight += weight; - this.sigops += sigops; - this.fees += item.fee; - - // Add the tx to our block - this.block.txs.push(tx); - this.items.push(item); - - // Update commitments. - this.refresh(); - - return true; -}; - -/** - * Hash until the nonce overflows. - * @returns {Boolean} Whether the nonce was found. - */ - -MinerBlock.prototype.findNonce = function findNonce() { - 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 = 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. - * @method - * @returns {Promise} - Returns Boolean. - */ - -MinerBlock.prototype.findNonceAsync = co(function* findNonceAsync() { - var block = this.block; - var target = this.target; - var interval = MinerBlock.INTERVAL; - var min = 0; - var max = interval; - var data, nonce; - - while (max <= 0xffffffff) { - data = block.abbr(); - nonce = yield workerPool.mine(data, target, min, max); - - if (nonce !== -1) - break; - - if (this.destroyed) - return nonce; - - 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 = util.now(); - - for (;;) { - nonce = this.findNonce(); - - if (nonce !== -1) - break; - - this.iterate(); - } - - this.commit(nonce); - - return this.block; -}; - -/** - * Mine asynchronously until the block is found. - * @method - * @returns {Promise} - Returns {@link Block}. - */ - -MinerBlock.prototype.mineAsync = co(function* mineAsync() { - var nonce; - - // Track how long we've been at it. - this.begin = util.now(); - - for (;;) { - nonce = yield this.findNonceAsync(); - - if (nonce !== -1) - break; - - if (this.destroyed) - return; - - this.iterate(); - } - - this.commit(nonce); - - return this.block; -}); - -/** - * Increment extraNonce, rebuild merkletree. - */ - -MinerBlock.prototype.iterate = function iterate() { - // Keep track of our iterations. - this.iterations++; - - // Send progress report. - this.sendStatus(); - - // Overflow the nonce and increment the extraNonce. - this.updateNonce(); -}; - -/** - * Commit and finalize mined block. - * @returns {Block} - */ - -MinerBlock.prototype.commit = function commit(nonce) { - assert(!this.committed, 'Block is already committed.'); - this.committed = true; - this.block.nonce = nonce; - this.block.mutable = false; - this.coinbase.mutable = false; - return this.block; -}; - -/** - * Snapshot the nonces. - * @returns {Nonces} - */ - -MinerBlock.prototype.snapshot = function snapshot() { - return new Nonces(this.nonce1, this.nonce2); -}; - -/** - * Send a progress report (emits `status`). - */ - -MinerBlock.prototype.sendStatus = function sendStatus() { - this.emit('status', { - block: this.block, - target: this.block.bits, - hashes: this.getHashes(), - hashrate: this.getRate(), - height: this.height, - best: util.revHex(this.tip.hash) - }); -}; - -/** - * Destroy the minerblock. Stop mining. - */ - -MinerBlock.prototype.destroy = function destroy() { - this.destroyed = true; -}; - -/** - * BlockEntry - * @alias module:mining.BlockEntry - * @constructor - * @param {TX} tx - * @property {TX} tx - * @property {Hash} hash - * @property {Amount} fee - * @property {Rate} rate - * @property {Number} priority - * @property {Boolean} free - * @property {Sigops} sigops - * @property {Number} depCount - */ - -function BlockEntry(tx) { - this.tx = tx; - this.hash = tx.hash('hex'); - this.fee = 0; - this.rate = 0; - this.priority = 0; - this.free = false; - this.sigops = 0; - this.descRate = 0; - this.depCount = 0; -} - -/** - * Instantiate block entry from transaction. - * @param {TX} tx - * @param {CoinView} view - * @param {MinerBlock} attempt - * @returns {BlockEntry} - */ - -BlockEntry.fromTX = function fromTX(tx, view, attempt) { - var item = new BlockEntry(tx); - item.fee = tx.getFee(view); - item.rate = tx.getRate(view); - item.priority = tx.getPriority(view, attempt.height); - item.free = false; - item.sigops = tx.getSigopsCost(view, attempt.flags); - item.descRate = item.rate; - return item; -}; - -/** - * Instantiate block entry from mempool entry. - * @param {MempoolEntry} entry - * @param {MinerBlock} attempt - * @returns {BlockEntry} - */ - -BlockEntry.fromEntry = function fromEntry(entry, attempt) { - var item = new BlockEntry(entry.tx); - item.fee = entry.getFee(); - item.rate = entry.getRate(); - item.priority = entry.getPriority(attempt.height); - item.free = item.fee < policy.getMinFee(entry.size); - item.sigops = entry.sigops; - item.descRate = entry.getDescRate(); - return item; -}; - -/** - * Nonces - * @constructor - * @ignore - */ - -function Nonces(nonce1, nonce2) { - this.nonce1 = nonce1; - this.nonce2 = nonce2; -} - -/** - * Inject nonces into miner block. - * @param {MinerBlock} attempt - * @param {Number} nonce - * @param {Number} ts - */ - -Nonces.prototype.inject = function inject(attempt, nonce, ts) { - attempt.block.ts = ts; - attempt.block.nonce = nonce; - attempt.nonce1 = this.nonce1; - attempt.nonce2 = this.nonce2; - attempt.updateCoinbase(); - attempt.updateMerkle(); -}; - -/** - * Attempt to verify POW. - * @param {MinerBlock} attempt - * @param {Number} nonce - * @param {Number} ts - */ - -Nonces.prototype.verify = function verify(attempt, nonce, ts) { - var block = attempt.block; - var bts = block.ts; - var bnonce = block.nonce; - var nonce1 = attempt.nonce1; - var nonce2 = attempt.nonce2; - var result; - - this.inject(attempt, nonce, ts); - - if (consensus.verifyPOW(block.hash(), block.bits)) - return true; - - this.inject(attempt, bnonce, bts); - - return false; -}; - -/* - * Expose - */ - -exports = MinerBlock; -exports.MinerBlock = MinerBlock; -exports.BlockEntry = BlockEntry; - -module.exports = exports; diff --git a/lib/mining/template.js b/lib/mining/template.js new file mode 100644 index 00000000..59d8991d --- /dev/null +++ b/lib/mining/template.js @@ -0,0 +1,621 @@ +/*! + * template.js - block template object for bcoin + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var assert = require('assert'); +var BN = require('bn.js'); +var util = require('../utils/util'); +var crypto = require('../crypto/crypto'); +var StaticWriter = require('../utils/staticwriter'); +var Address = require('../primitives/address'); +var TX = require('../primitives/tx'); +var Block = require('../primitives/block'); +var Input = require('../primitives/input'); +var Output = require('../primitives/output'); +var consensus = require('../protocol/consensus'); +var policy = require('../protocol/policy'); +var encoding = require('../utils/encoding'); +var CoinView = require('../coins/coinview'); +var DUMMY = new Buffer(0); + +/** + * Block Template + * @alias module:mining.BlockTemplate + * @constructor + * @param {Object} options + */ + +function BlockTemplate(options) { + if (!(this instanceof BlockTemplate)) + return new BlockTemplate(options); + + this.prevBlock = options.prevBlock; + this.version = options.version; + this.height = options.height; + this.ts = options.ts; + this.bits = options.bits; + this.target = consensus.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32); + this.locktime = options.locktime; + this.flags = options.flags; + this.coinbaseFlags = options.coinbaseFlags; + this.witness = options.witness; + this.address = options.address; + this.sigops = options.sigops; + this.weight = options.weight; + this.reward = consensus.getReward(this.height, options.halvingInterval); + this.tree = new MerkleTree(); + this.left = DUMMY; + this.right = DUMMY; + this.fees = 0; + this.items = []; +} + +/** + * Create witness commitment hash. + * @returns {Buffer} + */ + +BlockTemplate.prototype.commitmentHash = function commitmentHash() { + var nonce = encoding.ZERO_HASH; + var leaves = []; + var i, item, root, data; + + leaves.push(encoding.ZERO_HASH); + + for (i = 0; i < this.items.length; i++) { + item = this.items[i]; + leaves.push(item.tx.witnessHash()); + } + + root = crypto.createMerkleRoot(leaves); + + assert(!root.malleated); + + data = util.concat(root.hash, nonce); + + return crypto.hash256(data); +}; + +/** + * Calculate the block reward. + * @returns {Amount} + */ + +BlockTemplate.prototype.getReward = function getReward() { + return this.reward + this.fees; +}; + +/** + * Initialize the default coinbase. + * @returns {TX} + */ + +BlockTemplate.prototype.createCoinbase = function createCoinbase() { + var scale = consensus.WITNESS_SCALE_FACTOR; + var cb = new TX(); + var padding = 0; + var input, output, commit, hash; + + // Coinbase input. + input = new Input(); + + // Height (required in v2+ blocks) + input.script.set(0, new BN(this.height)); + + // Let the world know this little + // miner succeeded. + input.script.set(1, encoding.ZERO_HASH160); + + // Smaller nonce for good measure. + input.script.set(2, util.nonce().slice(0, 4)); + + // extraNonce - incremented when + // the nonce overflows. + input.script.set(3, extraNonce(0, 0)); + + input.script.compile(); + + // Set up the witness nonce. + if (this.witness) { + input.witness.set(0, encoding.ZERO_HASH); + input.witness.compile(); + } + + cb.inputs.push(input); + + // Reward output. + output = new Output(); + output.script.fromPubkeyhash(encoding.ZERO_HASH160); + output.value = this.getReward(); + + cb.outputs.push(output); + + // If we're using segwit, we + // need to set up the commitment. + if (this.witness) { + // Commitment output. + commit = new Output(); + hash = this.commitmentHash(); + commit.script.fromCommitment(hash); + cb.outputs.push(commit); + } + + // Padding for the CB height (constant size). + padding = 5 - input.script.code[0].getSize(); + assert(padding >= 0); + + // Reserved size. + // Without segwit: + // CB weight = 500 + // CB stripped size = 125 + // CB size = 125 + // Sigops cost = 4 + // With segwit: + // CB weight = 724 + // CB stripped size = 172 + // CB size = 208 + // Sigops cost = 4 + if (!this.witness) { + assert.equal(cb.getWeight() + padding * scale, 500); + assert.equal(cb.getBaseSize() + padding, 125); + assert.equal(cb.getSize() + padding, 125); + } else { + assert.equal(cb.getWeight() + padding * scale, 724); + assert.equal(cb.getBaseSize() + padding, 172); + assert.equal(cb.getSize() + padding, 208); + } + + // Setup coinbase flags (variable size). + input.script.set(1, this.coinbaseFlags); + input.script.compile(); + + // Setup output script (variable size). + output.script.clear(); + output.script.fromAddress(this.address); + + cb.refresh(); + + return cb; +}; + +/** + * Refresh the coinbase and merkle tree. + */ + +BlockTemplate.prototype.refresh = function refresh() { + var cb = this.createCoinbase(); + var raw = cb.toNormal(); + var size = 0; + var left, right; + + size += 4; // version + size += 1; // varint inputs length + size += cb.inputs[0].getSize(); // input size + size -= 4 + 4 + 4; // -(nonce1 + nonce2 + sequence) + + // Cut off right after the nonce + // push and before the sequence. + left = raw.slice(0, size); + + // Include the sequence. + size += 4 + 4; // nonce1 + nonce2 + right = raw.slice(size); + + this.left = left; + this.right = right; + this.tree = MerkleTree.fromItems(this.items); +}; + +/** + * Get raw coinbase with desired nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ + +BlockTemplate.prototype.getCoinbase = function getCoinbase(nonce1, nonce2) { + var size = 0; + var bw; + + size += this.left.length; + size += 4 + 4; + size += this.right.length; + + bw = new StaticWriter(size); + bw.writeBytes(this.left); + bw.writeU32BE(nonce1); + bw.writeU32BE(nonce2); + bw.writeBytes(this.right); + + return bw.render(); +}; + +/** + * Calculate the merkle root with given nonces. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {Buffer} + */ + +BlockTemplate.prototype.getRoot = function getRoot(nonce1, nonce2) { + var raw = this.getCoinbase(nonce1, nonce2); + var hash = crypto.hash256(raw); + return this.tree.withFirst(hash); +}; + +/** + * Create raw block header with given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} ts + * @param {Number} nonce + * @returns {Buffer} + */ + +BlockTemplate.prototype.getHeader = function getHeader(nonce1, nonce2, ts, nonce) { + var bw = new StaticWriter(80); + var root = this.getRoot(nonce1, nonce2); + + bw.writeU32(this.version); + bw.writeHash(this.prevBlock); + bw.writeHash(root); + bw.writeU32(ts); + bw.writeU32(this.bits); + bw.writeU32(nonce); + + return bw.render(); +}; + +/** + * Calculate block hash with given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} ts + * @param {Number} nonce + * @returns {Buffer} + */ + +BlockTemplate.prototype.hash = function hash(nonce1, nonce2, ts, nonce) { + var data = this.getHeader(nonce1, nonce2, ts, nonce); + return crypto.hash256(data); +}; + +/** + * Create coinbase from given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @returns {TX} + */ + +BlockTemplate.prototype.coinbase = function coinbase(nonce1, nonce2) { + var raw = this.getCoinbase(nonce1, nonce2); + var tx = TX.fromRaw(raw); + var input; + + if (this.witness) { + input = tx.inputs[0]; + input.witness.push(encoding.ZERO_HASH); + input.witness.compile(); + tx.refresh(); + } + + return tx; +}; + +/** + * Create block from given parameters. + * @param {Number} nonce1 + * @param {Number} nonce2 + * @param {Number} ts + * @param {Number} nonce + * @returns {Block} + */ + +BlockTemplate.prototype.commit = function commit(nonce1, nonce2, ts, nonce) { + var tx = this.coinbase(nonce1, nonce2); + var root = this.tree.withFirst(tx.hash()); + var block = new Block(); + var i, item; + + block.version = this.version; + block.prevBlock = this.prevBlock; + block.merkleRoot = root.toString('hex'); + block.ts = ts; + block.bits = this.bits; + block.nonce = nonce; + + block.txs.push(tx); + + for (i = 0; i < this.items.length; i++) { + item = this.items[i]; + block.txs.push(item.tx); + } + + return block; +}; + +/** + * Quick and dirty way to + * get a coinbase tx object. + * @returns {TX} + */ + +BlockTemplate.prototype.toCoinbase = function toCoinbase() { + return this.coinbase(0, 0); +}; + +/** + * Quick and dirty way to get a block + * object (most likely to be an invalid one). + * @returns {Block} + */ + +BlockTemplate.prototype.toBlock = function toBlock() { + return this.commit(0, 0, this.ts, 0); +}; + +/** + * Set the reward output + * address and refresh. + * @param {Address} address + */ + +BlockTemplate.prototype.setAddress = function setAddress(address) { + this.address = Address(address); + this.refresh(); +}; + +/** + * Add a transaction to the template. + * @param {TX} tx + * @param {CoinView} view + */ + +BlockTemplate.prototype.addTX = function addTX(tx, view) { + var item, weight, sigops; + + assert(!tx.mutable, 'Cannot add mutable TX to block.'); + + item = BlockEntry.fromTX(tx, view, this); + weight = item.tx.getWeight(); + sigops = item.sigops; + + if (!tx.isFinal(this.height, this.locktime)) + return false; + + if (this.weight + weight > consensus.MAX_BLOCK_WEIGHT) + return false; + + if (this.sigops + sigops > consensus.MAX_BLOCK_SIGOPS_COST) + return false; + + if (!this.witness && tx.hasWitness()) + return false; + + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; + + // Add the tx to our block + this.items.push(item); + + return true; +}; + +/** + * Add a transaction to the template + * (less verification than addTX). + * @param {TX} tx + * @param {CoinView?} view + */ + +BlockTemplate.prototype.pushTX = function pushTX(tx, view) { + var item, weight, sigops; + + assert(!tx.mutable, 'Cannot add mutable TX to block.'); + + if (!view) + view = new CoinView(); + + item = BlockEntry.fromTX(tx, view, this); + weight = item.tx.getWeight(); + sigops = item.sigops; + + this.weight += weight; + this.sigops += sigops; + this.fees += item.fee; + + // Add the tx to our block + this.items.push(item); + + return true; +}; + +/** + * BlockEntry + * @alias module:mining.BlockEntry + * @constructor + * @param {TX} tx + * @property {TX} tx + * @property {Hash} hash + * @property {Amount} fee + * @property {Rate} rate + * @property {Number} priority + * @property {Boolean} free + * @property {Sigops} sigops + * @property {Number} depCount + */ + +function BlockEntry(tx) { + this.tx = tx; + this.hash = tx.hash('hex'); + this.fee = 0; + this.rate = 0; + this.priority = 0; + this.free = false; + this.sigops = 0; + this.descRate = 0; + this.depCount = 0; +} + +/** + * Instantiate block entry from transaction. + * @param {TX} tx + * @param {CoinView} view + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + +BlockEntry.fromTX = function fromTX(tx, view, attempt) { + var item = new BlockEntry(tx); + item.fee = tx.getFee(view); + item.rate = tx.getRate(view); + item.priority = tx.getPriority(view, attempt.height); + item.free = false; + item.sigops = tx.getSigopsCost(view, attempt.flags); + item.descRate = item.rate; + return item; +}; + +/** + * Instantiate block entry from mempool entry. + * @param {MempoolEntry} entry + * @param {BlockTemplate} attempt + * @returns {BlockEntry} + */ + +BlockEntry.fromEntry = function fromEntry(entry, attempt) { + var item = new BlockEntry(entry.tx); + item.fee = entry.getFee(); + item.rate = entry.getRate(); + item.priority = entry.getPriority(attempt.height); + item.free = item.fee < policy.getMinFee(entry.size); + item.sigops = entry.sigops; + item.descRate = entry.getDescRate(); + return item; +}; + +/* + * MerkleTree + * @constructor + * @property {Hash[]} steps + */ + +function MerkleTree() { + this.steps = []; +} + +MerkleTree.prototype.withFirst = function withFirst(hash) { + var i, step, data; + + for (i = 0; i < this.steps.length; i++) { + step = this.steps[i]; + data = util.concat(hash, step); + hash = crypto.hash256(data); + } + + return hash; +}; + +MerkleTree.prototype.toJSON = function toJSON() { + var steps = []; + var i, step; + + for (i = 0; i < this.steps.length; i++) { + step = this.steps[i]; + steps.push(step.toString('hex')); + } + + return steps; +}; + +MerkleTree.prototype.fromItems = function fromItems(items) { + var leaves = []; + var i, item; + + leaves.push(encoding.ZERO_HASH); + + for (i = 0; i < items.length; i++) { + item = items[i]; + leaves.push(item.tx.hash()); + } + + return this.fromLeaves(leaves); +}; + +MerkleTree.fromItems = function fromItems(items) { + return new MerkleTree().fromItems(items); +}; + +MerkleTree.prototype.fromBlock = function fromBlock(txs) { + var leaves = []; + var i, tx; + + leaves.push(encoding.ZERO_HASH); + + for (i = 1; i < txs.length; i++) { + tx = txs[i]; + leaves.push(tx.hash()); + } + + return this.fromLeaves(leaves); +}; + +MerkleTree.fromBlock = function fromBlock(txs) { + return new MerkleTree().fromBlock(txs); +}; + +MerkleTree.prototype.fromLeaves = function fromLeaves(leaves) { + var len = leaves.length; + var i, hashes, data, hash; + + while (len > 1) { + this.steps.push(leaves[1]); + + if (len % 2) + leaves.push(leaves[len - 1]); + + hashes = [null]; + + for (i = 2; i < len; i += 2) { + data = util.concat(leaves[i], leaves[i + 1]); + hash = crypto.hash256(data); + hashes.push(hash); + } + + leaves = hashes; + len = leaves.length; + } + + return this; +}; + +MerkleTree.fromLeaves = function fromLeaves(leaves) { + return new MerkleTree().fromLeaves(leaves); +}; + +/* + * Helpers + */ + +function extraNonce(nonce1, nonce2) { + var bw = new StaticWriter(8); + bw.writeU32BE(nonce1); + bw.writeU32BE(nonce2); + return bw.render(); +}; + +/* + * Expose + */ + +exports = BlockTemplate; +exports.BlockTemplate = BlockTemplate; +exports.BlockEntry = BlockEntry; + +module.exports = exports; diff --git a/lib/utils/index.js b/lib/utils/index.js index a15c4d93..ced85e0f 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -12,6 +12,7 @@ exports.Bloom = require('./bloom'); exports.RollingFilter = exports.Bloom.Rolling; exports.co = require('./co'); exports.encoding = require('./encoding'); +exports.fs = require('./fs'); exports.Heap = require('./heap'); exports.IP = require('./ip'); exports.lazy = require('./lazy'); diff --git a/lib/wallet/rpc.js b/lib/wallet/rpc.js index 5a26a788..cf5b5dc3 100644 --- a/lib/wallet/rpc.js +++ b/lib/wallet/rpc.js @@ -15,7 +15,6 @@ var Amount = require('../btc/amount'); var Script = require('../script/script'); var Address = require('../primitives/address'); var KeyRing = require('../primitives/keyring'); -var Lock = require('../utils/lock'); var MerkleBlock = require('../primitives/merkleblock'); var MTX = require('../primitives/mtx'); var Outpoint = require('../primitives/outpoint'); diff --git a/test/chain-test.js b/test/chain-test.js index 39abf794..4e2afe08 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -14,6 +14,7 @@ var MemWallet = require('./util/memwallet'); var Network = require('../lib/protocol/network'); var Output = require('../lib/primitives/output'); var util = require('../lib/utils/util'); +var common = require('../lib/blockchain/common'); var opcodes = Script.opcodes; describe('Chain', function() { @@ -22,16 +23,16 @@ describe('Chain', function() { var miner = new Miner({ chain: chain, version: 4 }); var wallet = new MemWallet({ network: network }); var wwallet = new MemWallet({ network: network, witness: true }); - var tip1, tip2, addBlock, mineCSV; + var cpu = miner.cpu; + var tip1, tip2, addBlock, mineBlock, mineCSV; this.timeout(45000); - addBlock = co(function* addBlock(attempt) { - var block = yield attempt.mineAsync(); + addBlock = co(function* addBlock(block, flags) { var entry; try { - entry = yield chain.add(block); + entry = yield chain.add(block, flags); } catch (e) { assert(e.type === 'VerifyError'); return e.reason; @@ -43,8 +44,13 @@ describe('Chain', function() { return 'OK'; }); + mineBlock = co(function* mineBlock(job, flags) { + var block = yield job.mineAsync(); + return yield addBlock(block, flags); + }); + mineCSV = co(function* mineCSV(tx) { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var rtx; rtx = new MTX(); @@ -63,9 +69,10 @@ describe('Chain', function() { wallet.sign(rtx); - attempt.addTX(rtx.toTX(), rtx.view); + job.addTX(rtx.toTX(), rtx.view); + job.refresh(); - return yield attempt.mineAsync(); + return yield job.mineAsync(); }); chain.on('connect', function(entry, block) { @@ -90,7 +97,7 @@ describe('Chain', function() { var i, block; for (i = 0; i < 200; i++) { - block = yield miner.mineBlock(); + block = yield cpu.mineBlock(); assert(block); assert(yield chain.add(block)); } @@ -99,11 +106,11 @@ describe('Chain', function() { })); it('should mine competing chains', co(function* () { - var i, mtx, at1, at2, blk1, blk2, hash1, hash2; + var i, mtx, job1, job2, blk1, blk2, hash1, hash2; for (i = 0; i < 10; i++) { - at1 = yield miner.createBlock(tip1); - at2 = yield miner.createBlock(tip2); + job1 = yield cpu.createJob(tip1); + job2 = yield cpu.createJob(tip2); mtx = yield wallet.create({ outputs: [{ @@ -112,11 +119,14 @@ describe('Chain', function() { }] }); - at1.addTX(mtx.toTX(), mtx.view); - at2.addTX(mtx.toTX(), mtx.view); + job1.addTX(mtx.toTX(), mtx.view); + job2.addTX(mtx.toTX(), mtx.view); - blk1 = yield at1.mineAsync(); - blk2 = yield at2.mineAsync(); + job1.refresh(); + job2.refresh(); + + blk1 = yield job1.mineAsync(); + blk2 = yield job2.mineAsync(); hash1 = blk1.hash('hex'); hash2 = blk2.hash('hex'); @@ -156,7 +166,7 @@ describe('Chain', function() { assert(entry); assert(chain.height === entry.height); - block = yield miner.mineBlock(entry); + block = yield cpu.mineBlock(entry); assert(block); chain.once('reorganize', function() { @@ -186,7 +196,7 @@ describe('Chain', function() { })); it('should mine a block after a reorg', co(function* () { - var block = yield miner.mineBlock(); + var block = yield cpu.mineBlock(); var hash, entry, result; assert(yield chain.add(block)); @@ -202,7 +212,7 @@ describe('Chain', function() { })); it('should prevent double spend on new chain', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var mtx, block; mtx = yield wallet.create({ @@ -212,37 +222,40 @@ describe('Chain', function() { }] }); - attempt.addTX(mtx.toTX(), mtx.view); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); assert(yield chain.add(block)); - attempt = yield miner.createBlock(); + job = yield cpu.createJob(); assert(mtx.outputs.length > 1); mtx.outputs.pop(); - attempt.addTX(mtx.toTX(), mtx.view); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent'); + assert.equal(yield mineBlock(job), 'bad-txns-inputs-missingorspent'); })); it('should fail to connect coins on an alternate chain', co(function* () { var block = yield chain.db.getBlock(tip1.hash); var cb = block.txs[0]; var mtx = new MTX(); - var attempt; + var job; mtx.addTX(cb, 0); mtx.addOutput(wallet.getAddress(), 10 * 1e8); wallet.sign(mtx); - attempt = yield miner.createBlock(); - attempt.addTX(mtx.toTX(), mtx.view); + job = yield cpu.createJob(); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-txns-inputs-missingorspent'); + assert.equal(yield mineBlock(job), 'bad-txns-inputs-missingorspent'); })); it('should have correct chain value', function() { @@ -252,7 +265,7 @@ describe('Chain', function() { }); it('should get coin', co(function* () { - var mtx, attempt, block, tx, output, coin; + var mtx, job, block, tx, output, coin; mtx = yield wallet.send({ outputs: [ @@ -271,10 +284,11 @@ describe('Chain', function() { ] }); - attempt = yield miner.createBlock(); - attempt.addTX(mtx.toTX(), mtx.view); + job = yield cpu.createJob(); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); assert(yield chain.add(block)); tx = block.txs[1]; @@ -330,7 +344,7 @@ describe('Chain', function() { assert.equal(state, 1); for (i = 0; i < 417; i++) { - block = yield miner.mineBlock(); + block = yield cpu.mineBlock(); assert(yield chain.add(block)); switch (chain.height) { case 288: @@ -371,7 +385,7 @@ describe('Chain', function() { it('should test csv', co(function* () { var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0]; var block = yield mineCSV(tx); - var csv, attempt, rtx; + var csv, job, rtx; assert(yield chain.add(block)); @@ -390,11 +404,12 @@ describe('Chain', function() { rtx.addTX(csv, 0); rtx.setSequence(0, 1, false); - attempt = yield miner.createBlock(); + job = yield cpu.createJob(); - attempt.addTX(rtx.toTX(), rtx.view); + job.addTX(rtx.toTX(), rtx.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); assert(yield chain.add(block)); })); @@ -402,7 +417,7 @@ describe('Chain', function() { it('should fail csv with bad sequence', co(function* () { var csv = (yield chain.db.getBlock(chain.height - 100)).txs[0]; var rtx = new MTX(); - var attempt; + var job; rtx.addOutput({ script: [ @@ -415,14 +430,15 @@ describe('Chain', function() { rtx.addTX(csv, 0); rtx.setSequence(0, 1, false); - attempt = yield miner.createBlock(); - attempt.addTX(rtx.toTX(), rtx.view); + job = yield cpu.createJob(); + job.addTX(rtx.toTX(), rtx.view); + job.refresh(); - assert.equal(yield addBlock(attempt), 'mandatory-script-verify-flag-failed'); + assert.equal(yield mineBlock(job), 'mandatory-script-verify-flag-failed'); })); it('should mine a block', co(function* () { - var block = yield miner.mineBlock(); + var block = yield cpu.mineBlock(); assert(block); assert(yield chain.add(block)); })); @@ -430,7 +446,7 @@ describe('Chain', function() { it('should fail csv lock checks', co(function* () { var tx = (yield chain.db.getBlock(chain.height - 100)).txs[0]; var block = yield mineCSV(tx); - var csv, attempt, rtx; + var csv, job, rtx; assert(yield chain.add(block)); @@ -449,10 +465,11 @@ describe('Chain', function() { rtx.addTX(csv, 0); rtx.setSequence(0, 2, false); - attempt = yield miner.createBlock(); - attempt.addTX(rtx.toTX(), rtx.view); + job = yield cpu.createJob(); + job.addTX(rtx.toTX(), rtx.view); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal'); + assert.equal(yield mineBlock(job), 'bad-txns-nonfinal'); })); it('should have correct wallet balance', co(function* () { @@ -460,72 +477,72 @@ describe('Chain', function() { })); it('should fail to connect bad bits', co(function* () { - var attempt = yield miner.createBlock(); - attempt.block.bits = 553713663; - assert.equal(yield addBlock(attempt), 'bad-diffbits'); + var job = yield cpu.createJob(); + job.attempt.bits = 553713663; + assert.equal(yield mineBlock(job), 'bad-diffbits'); })); it('should fail to connect bad MTP', co(function* () { var mtp = yield chain.tip.getMedianTime(); - var attempt = yield miner.createBlock(); - attempt.block.ts = mtp - 1; - assert.equal(yield addBlock(attempt), 'time-too-old'); + var job = yield cpu.createJob(); + job.attempt.ts = mtp - 1; + assert.equal(yield mineBlock(job), 'time-too-old'); })); it('should fail to connect bad time', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var now = network.now() + 3 * 60 * 60; - attempt.block.ts = now; - assert.equal(yield addBlock(attempt), 'time-too-new'); + job.attempt.ts = now; + assert.equal(yield mineBlock(job), 'time-too-new'); })); it('should fail to connect bad locktime', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var tx = yield wallet.send({ locktime: 100000 }); - attempt.block.txs.push(tx.toTX()); - attempt.refresh(); - assert.equal(yield addBlock(attempt), 'bad-txns-nonfinal'); + job.pushTX(tx.toTX()); + job.refresh(); + assert.equal(yield mineBlock(job), 'bad-txns-nonfinal'); })); it('should fail to connect bad cb height', co(function* () { var bip34height = network.block.bip34height; - var attempt = yield miner.createBlock(); - var tx = attempt.block.txs[0]; - var input = tx.inputs[0]; + var job = yield cpu.createJob(); - input.script.set(0, new BN(10)); - input.script.compile(); - attempt.refresh(); + job.attempt.height = 10; + job.attempt.refresh(); try { network.block.bip34height = 0; - assert.equal(yield addBlock(attempt), 'bad-cb-height'); + assert.equal(yield mineBlock(job), 'bad-cb-height'); } finally { network.block.bip34height = bip34height; } })); it('should fail to connect bad witness nonce size', co(function* () { - var attempt = yield miner.createBlock(); - var tx = attempt.block.txs[0]; + var block = yield cpu.mineBlock(); + var tx = block.txs[0]; var input = tx.inputs[0]; input.witness.set(0, new Buffer(33)); input.witness.compile(); - assert.equal(yield addBlock(attempt), 'bad-witness-nonce-size'); + block.refresh(true); + assert.equal(yield addBlock(block), 'bad-witness-nonce-size'); })); it('should fail to connect bad witness nonce', co(function* () { - var attempt = yield miner.createBlock(); - var tx = attempt.block.txs[0]; + var block = yield cpu.mineBlock(); + var tx = block.txs[0]; var input = tx.inputs[0]; input.witness.set(0, encoding.ONE_HASH); input.witness.compile(); - assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match'); + block.refresh(true); + assert.equal(yield addBlock(block), 'bad-witness-merkle-match'); })); it('should fail to connect bad witness commitment', co(function* () { - var attempt = yield miner.createBlock(); - var tx = attempt.block.txs[0]; + var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; + var block = yield cpu.mineBlock(); + var tx = block.txs[0]; var output = tx.outputs[1]; var commit; @@ -536,18 +553,26 @@ describe('Chain', function() { output.script.set(1, commit); output.script.compile(); - attempt.updateMerkle(); - assert.equal(yield addBlock(attempt), 'bad-witness-merkle-match'); + block.refresh(true); + block.merkleRoot = block.createMerkleRoot('hex'); + + assert.equal(yield addBlock(block, flags), 'bad-witness-merkle-match'); })); it('should fail to connect unexpected witness', co(function* () { - var attempt = yield miner.createBlock(); - var tx = attempt.block.txs[0]; + var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; + var block = yield cpu.mineBlock(); + var tx = block.txs[0]; var output = tx.outputs[1]; + assert(output.script.isCommitment()); + tx.outputs.pop(); - attempt.updateMerkle(); - assert.equal(yield addBlock(attempt), 'unexpected-witness'); + + block.refresh(true); + block.merkleRoot = block.createMerkleRoot('hex'); + + assert.equal(yield addBlock(block, flags), 'unexpected-witness'); })); it('should add wit addrs to miner', co(function* () { @@ -560,7 +585,7 @@ describe('Chain', function() { var i, block; for (i = 0; i < 2001; i++) { - block = yield miner.mineBlock(); + block = yield cpu.mineBlock(); assert(block); assert(yield chain.add(block)); } @@ -572,17 +597,18 @@ describe('Chain', function() { var block = yield chain.db.getBlock(chain.height - 2000); var cb = block.txs[0]; var mtx = new MTX(); - var attempt; + var job; mtx.addTX(cb, 0); mtx.addOutput(wwallet.getAddress(), 1000); wwallet.sign(mtx); - attempt = yield miner.createBlock(); - attempt.addTX(mtx.toTX(), mtx.view); + job = yield cpu.createJob(); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); assert(yield chain.add(block)); })); @@ -590,7 +616,7 @@ describe('Chain', function() { it('should mine fail to connect too much weight', co(function* () { var start = chain.height - 2000; var end = chain.height - 200; - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var mtx = new MTX(); var i, j, block, cb; @@ -606,18 +632,18 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.block.txs.push(mtx.toTX()); + job.pushTX(mtx.toTX()); } - attempt.refresh(); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-blk-weight'); + assert.equal(yield mineBlock(job), 'bad-blk-weight'); })); it('should mine fail to connect too much size', co(function* () { var start = chain.height - 2000; var end = chain.height - 200; - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var mtx = new MTX(); var i, j, block, cb; @@ -633,18 +659,18 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.block.txs.push(mtx.toTX()); + job.pushTX(mtx.toTX()); } - attempt.refresh(); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-blk-length'); + assert.equal(yield mineBlock(job), 'bad-blk-length'); })); it('should mine a big block', co(function* () { var start = chain.height - 2000; var end = chain.height - 200; - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var mtx = new MTX(); var i, j, block, cb; @@ -660,34 +686,34 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.block.txs.push(mtx.toTX()); + job.pushTX(mtx.toTX()); } - attempt.refresh(); + job.refresh(); - assert.equal(yield addBlock(attempt), 'OK'); + assert.equal(yield mineBlock(job), 'OK'); })); it('should fail to connect bad versions', co(function* () { - var i, attempt; + var i, job; for (i = 0; i <= 3; i++) { - attempt = yield miner.createBlock(); - attempt.block.version = i; - assert.equal(yield addBlock(attempt), 'bad-version'); + job = yield cpu.createJob(); + job.attempt.version = i; + assert.equal(yield mineBlock(job), 'bad-version'); } })); it('should fail to connect bad amount', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); - attempt.block.txs[0].outputs[0].value += 1; - attempt.updateMerkle(); - assert.equal(yield addBlock(attempt), 'bad-cb-amount'); + job.attempt.reward += 1; + job.refresh(); + assert.equal(yield mineBlock(job), 'bad-cb-amount'); })); it('should fail to connect premature cb spend', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var block = yield chain.db.getBlock(chain.height - 98); var cb = block.txs[0]; var mtx = new MTX(); @@ -697,14 +723,15 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.addTX(mtx.toTX(), mtx.view); + job.addTX(mtx.toTX(), mtx.view); + job.refresh(); - assert.equal(yield addBlock(attempt), + assert.equal(yield mineBlock(job), 'bad-txns-premature-spend-of-coinbase'); })); it('should fail to connect vout belowout', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var block = yield chain.db.getBlock(chain.height - 99); var cb = block.txs[0]; var mtx = new MTX(); @@ -714,15 +741,15 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.block.txs.push(mtx.toTX()); - attempt.refresh(); + job.pushTX(mtx.toTX()); + job.refresh(); - assert.equal(yield addBlock(attempt), + assert.equal(yield mineBlock(job), 'bad-txns-in-belowout'); })); it('should fail to connect outtotal toolarge', co(function* () { - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var block = yield chain.db.getBlock(chain.height - 99); var cb = block.txs[0]; var mtx = new MTX(); @@ -734,15 +761,16 @@ describe('Chain', function() { wwallet.sign(mtx); - attempt.block.txs.push(mtx.toTX()); - attempt.refresh(); + job.pushTX(mtx.toTX()); + job.refresh(); - assert.equal(yield addBlock(attempt), + assert.equal(yield mineBlock(job), 'bad-txns-txouttotal-toolarge'); })); it('should mine 111 multisig blocks', co(function* () { - var i, j, script, attempt, cb, output, val, block; + var flags = common.flags.DEFAULT_FLAGS & ~common.flags.VERIFY_POW; + var i, j, script, job, cb, output, val, block; script = new Script(); script.push(new BN(20)); @@ -757,8 +785,8 @@ describe('Chain', function() { script = Script.fromScripthash(script.hash160()); for (i = 0; i < 111; i++) { - attempt = yield miner.createBlock(); - cb = attempt.block.txs[0]; + block = yield cpu.mineBlock(); + cb = block.txs[0]; val = cb.outputs[0].value; cb.outputs[0].value = 0; @@ -771,10 +799,10 @@ describe('Chain', function() { cb.outputs.push(output); } - attempt.updateMerkle(); + block.refresh(true); + block.merkleRoot = block.createMerkleRoot('hex'); - block = yield attempt.mineAsync(); - assert(yield chain.add(block)); + assert(yield chain.add(block, flags)); } assert.equal(chain.height, 2749); @@ -783,7 +811,7 @@ describe('Chain', function() { it('should fail to connect too many sigops', co(function* () { var start = chain.height - 110; var end = chain.height - 100; - var attempt = yield miner.createBlock(); + var job = yield cpu.createJob(); var i, j, mtx, script, block, cb; script = new Script(); @@ -812,12 +840,12 @@ describe('Chain', function() { mtx.addOutput(wwallet.getAddress(), 1); - attempt.block.txs.push(mtx.toTX()); + job.pushTX(mtx.toTX()); } - attempt.refresh(); + job.refresh(); - assert.equal(yield addBlock(attempt), 'bad-blk-sigops'); + assert.equal(yield mineBlock(job), 'bad-blk-sigops'); })); it('should cleanup', co(function* () { diff --git a/test/node-test.js b/test/node-test.js index c9a319a0..64e3b4a4 100644 --- a/test/node-test.js +++ b/test/node-test.js @@ -8,7 +8,6 @@ var Coin = require('../lib/primitives/coin'); var Script = require('../lib/script/script'); var FullNode = require('../lib/node/fullnode'); var MTX = require('../lib/primitives/mtx'); -// var Client = require('../lib/wallet/client'); describe('Node', function() { var node = new FullNode({ @@ -23,18 +22,16 @@ describe('Node', function() { var miner = node.miner; var wallet, tip1, tip2, cb1, cb2, mineBlock; - // walletdb.client = new Client({ apiKey: 'foo', network: 'regtest' }); - node.on('error', function() {}); this.timeout(5000); mineBlock = co(function* mineBlock(tip, tx) { - var attempt = yield miner.createBlock(tip); + var job = yield miner.createJob(tip); var rtx; if (!tx) - return yield attempt.mineAsync(); + return yield job.mineAsync(); rtx = new MTX(); @@ -47,9 +44,10 @@ describe('Node', function() { yield wallet.sign(rtx); - attempt.addTX(rtx.toTX(), rtx.view); + job.addTX(rtx.toTX(), rtx.view); + job.refresh(); - return yield attempt.mineAsync(); + return yield job.mineAsync(); }); it('should open chain and miner', co(function* () { @@ -312,7 +310,7 @@ describe('Node', function() { })); var mineCSV = co(function* mineCSV(tx) { - var attempt = yield miner.createBlock(); + var job = yield miner.createJob(); var redeemer; redeemer = new MTX(); @@ -331,15 +329,16 @@ describe('Node', function() { yield wallet.sign(redeemer); - attempt.addTX(redeemer.toTX(), redeemer.view); + job.addTX(redeemer.toTX(), redeemer.view); + job.refresh(); - return yield attempt.mineAsync(); + return yield job.mineAsync(); }); it('should test csv', co(function* () { var tx = (yield chain.db.getBlock(chain.height)).txs[0]; var block = yield mineCSV(tx); - var csv, attempt, redeemer; + var csv, job, redeemer; yield chain.add(block); @@ -358,18 +357,19 @@ describe('Node', function() { redeemer.addTX(csv, 0); redeemer.setSequence(0, 1, false); - attempt = yield miner.createBlock(); + job = yield miner.createJob(); - attempt.addTX(redeemer.toTX(), redeemer.view); + job.addTX(redeemer.toTX(), redeemer.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); yield chain.add(block); })); it('should fail csv with bad sequence', co(function* () { var csv = (yield chain.db.getBlock(chain.height)).txs[1]; - var block, attempt, redeemer, err; + var block, job, redeemer, err; redeemer = new MTX(); @@ -384,11 +384,12 @@ describe('Node', function() { redeemer.addTX(csv, 0); redeemer.setSequence(0, 1, false); - attempt = yield miner.createBlock(); + job = yield miner.createJob(); - attempt.addTX(redeemer.toTX(), redeemer.view); + job.addTX(redeemer.toTX(), redeemer.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); try { yield chain.add(block); @@ -409,7 +410,7 @@ describe('Node', function() { it('should fail csv lock checks', co(function* () { var tx = (yield chain.db.getBlock(chain.height)).txs[0]; var block = yield mineCSV(tx); - var csv, attempt, redeemer, err; + var csv, job, redeemer, err; yield chain.add(block); @@ -428,11 +429,12 @@ describe('Node', function() { redeemer.addTX(csv, 0); redeemer.setSequence(0, 2, false); - attempt = yield miner.createBlock(); + job = yield miner.createJob(); - attempt.addTX(redeemer.toTX(), redeemer.view); + job.addTX(redeemer.toTX(), redeemer.view); + job.refresh(); - block = yield attempt.mineAsync(); + block = yield job.mineAsync(); try { yield chain.add(block);