From bc00697adbe6ae3c4ea5960b1cd2c00279fb23a7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 18 Nov 2016 06:20:30 -0800 Subject: [PATCH] mempool/miner: mining and mempool refactor. --- lib/http/rpc.js | 155 ++++++++++++++------------ lib/mempool/mempool.js | 155 +++++++++----------------- lib/mempool/mempoolentry.js | 35 ++---- lib/miner/miner.js | 217 +++++++++++++++++++++++++++--------- lib/miner/minerblock.js | 68 +++++------ lib/node/node.js | 8 +- lib/primitives/tx.js | 76 +++++++++++++ lib/wallet/wallet.js | 3 + lib/wallet/walletdb.js | 8 +- test/chain-test.js | 3 +- 10 files changed, 424 insertions(+), 304 deletions(-) diff --git a/lib/http/rpc.js b/lib/http/rpc.js index 85092c09..86d74864 100644 --- a/lib/http/rpc.js +++ b/lib/http/rpc.js @@ -338,8 +338,8 @@ RPC.prototype.getinfo = co(function* getinfo(args) { keypoololdest: 0, keypoolsize: 0, unlocked_until: this.wallet.master.until, - paytxfee: +utils.btc(this.network.getRate()), - relayfee: +utils.btc(this.network.getMinRelay()), + paytxfee: +utils.btc(this.network.feeRate), + relayfee: +utils.btc(this.network.minRelay), errors: '' }; }); @@ -1312,6 +1312,15 @@ RPC.prototype.verifychain = function verifychain(args) { */ RPC.prototype._submitwork = co(function* _submitwork(data) { + var unlock = yield this.locker.lock(); + try { + return yield this.__submitwork(data); + } finally { + unlock(); + } +}); + +RPC.prototype.__submitwork = co(function* _submitwork(data) { var attempt = this.attempt; var block, header, cb, cur; @@ -1368,7 +1377,16 @@ RPC.prototype._submitwork = co(function* _submitwork(data) { return true; }); -RPC.prototype._getwork = co(function* _getwork() { +RPC.prototype._creatework = co(function* _creatework(data) { + var unlock = yield this.locker.lock(); + try { + return yield this.__creatework(data); + } finally { + unlock(); + } +}); + +RPC.prototype.__creatework = co(function* _creatework() { var attempt = yield this._getAttempt(true); var data, abbr; @@ -1392,55 +1410,32 @@ RPC.prototype._getwork = co(function* _getwork() { RPC.prototype.getworklp = co(function* getworklp(args) { yield this._onBlock(); - return yield this._getwork(); + return yield this._creatework(); }); RPC.prototype.getwork = co(function* getwork(args) { - var unlock = yield this.locker.lock(); - var data, result; + var data; - if (args.length > 1) { - unlock(); + if (args.length > 1) throw new RPCError('getwork ( "data" )'); - } if (args.length === 1) { - if (!utils.isHex(args[0])) { - unlock(); + if (!utils.isHex(args[0])) throw new RPCError('Invalid parameter.'); - } data = new Buffer(args[0], 'hex'); - try { - result = yield this._submitwork(data); - } catch (e) { - unlock(); - throw e; - } - - return result; + return yield this._submitwork(data); } - try { - result = yield this._getwork(); - } catch (e) { - unlock(); - throw e; - } - - unlock(); - return result; + return yield this._creatework(); }); RPC.prototype.submitblock = co(function* submitblock(args) { - var unlock = yield this.locker.lock(); var block; - if (args.help || args.length < 1 || args.length > 2) { - unlock(); + if (args.help || args.length < 1 || args.length > 2) throw new RPCError('submitblock "hexdata" ( "jsonparametersobject" )'); - } block = Block.fromRaw(toString(args[0]), 'hex'); @@ -1448,6 +1443,15 @@ RPC.prototype.submitblock = co(function* submitblock(args) { }); RPC.prototype._submitblock = co(function* submitblock(block) { + var unlock = yield this.locker.lock(); + try { + return yield this.__submitblock(block); + } finally { + unlock(); + } +}); + +RPC.prototype.__submitblock = co(function* submitblock(block) { if (block.prevBlock !== this.chain.tip.hash) return 'rejected: inconclusive-not-best-prevblk'; @@ -1528,36 +1532,43 @@ RPC.prototype.getblocktemplate = co(function* getblocktemplate(args) { yield this._poll(lpid); - return yield this._tmpl(version, coinbase, rules); + return yield this._template(version, coinbase, rules); }); -RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) { +RPC.prototype._template = co(function* _template(version, coinbase, rules) { var unlock = yield this.locker.lock(); + try { + return yield this.__template(version, coinbase, rules); + } finally { + unlock(); + } +}); + +RPC.prototype.__template = co(function* _template(version, coinbase, rules) { var txs = []; var txIndex = {}; - var i, j, tx, deps, input, dep, block, output, raw, rwhash; - var keys, vbavailable, vbrules, mutable, template, attempt; + var attempt = yield this._getAttempt(false); + var block = attempt.block; + var i, j, tx, deps, input, dep, output, raw, rwhash; + var keys, vbavailable, vbrules, mutable, template; var id, deployment, state; - try { - attempt = yield this._getAttempt(false); - } catch (e) { - unlock(); - throw e; - } - - block = attempt.block; - for (i = 1; i < block.txs.length; i++) { tx = block.txs[i]; txIndex[tx.hash('hex')] = i; + } + + for (i = 1; i < block.txs.length; i++) { + tx = block.txs[i]; deps = []; for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; dep = txIndex[input.prevout.hash]; - if (dep != null) + if (dep != null && deps.indexOf(dep) === -1) { + assert(dep < i); deps.push(dep); + } } txs.push({ @@ -1600,10 +1611,8 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) { case constants.thresholdStates.ACTIVE: vbrules.push(id); if (rules) { - if (rules.indexOf(id) === -1 && !deployment.force) { - unlock(); + if (rules.indexOf(id) === -1 && !deployment.force) throw new RPCError('Client must support ' + id + '.'); - } } break; } @@ -1675,7 +1684,6 @@ RPC.prototype._tmpl = co(function* _tmpl(version, coinbase, rules) { template.default_witness_commitment = output.script.toJSON(); } - unlock(); return template; }); @@ -1918,24 +1926,30 @@ RPC.prototype.setgenerate = co(function* setgenerate(args) { RPC.prototype.generate = co(function* generate(args) { var unlock = yield this.locker.lock(); + try { + return yield this._generate(args); + } finally { + unlock(); + } +}); + +RPC.prototype._generate = co(function* generate(args) { var numblocks; - if (args.help || args.length < 1 || args.length > 2) { - unlock(); + if (args.help || args.length < 1 || args.length > 2) throw new RPCError('generate numblocks ( maxtries )'); - } numblocks = toNumber(args[0], 1); - return yield this._generate(numblocks); + return yield this._generateBlocks(numblocks); }); -RPC.prototype._generate = co(function* _generate(numblocks) { +RPC.prototype._generateBlocks = co(function* _generateBlocks(blocks, address) { var hashes = []; var i, block; - for (i = 0; i < numblocks; i++) { - block = yield this.miner.mineBlock(); + for (i = 0; i < blocks; i++) { + block = yield this.miner.mineBlock(null, address); hashes.push(block.rhash); yield this.chain.add(block); } @@ -1945,24 +1959,23 @@ RPC.prototype._generate = co(function* _generate(numblocks) { RPC.prototype.generatetoaddress = co(function* generatetoaddress(args) { var unlock = yield this.locker.lock(); - var numblocks, address, hashes; - - if (args.help || args.length < 2 || args.length > 3) { + try { + return yield this._generatetoaddress(args); + } finally { unlock(); - throw new RPCError('generatetoaddress numblocks address ( maxtries )'); } +}); + +RPC.prototype._generatetoaddress = co(function* generatetoaddress(args) { + var numblocks, address; + + if (args.help || args.length < 2 || args.length > 3) + throw new RPCError('generatetoaddress numblocks address ( maxtries )'); numblocks = toNumber(args[0], 1); - address = this.miner.address; + address = Address.fromBase58(toString(args[1])); - this.miner.address = Address.fromBase58(toString(args[1])); - - hashes = yield this._generate(numblocks); - - this.miner.address = address; - - unlock(); - return hashes; + return yield this._generateBlocks(numblocks, address); }); /* diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index b8b2c7b5..336265d1 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -46,10 +46,6 @@ var MempoolEntry = require('./mempoolentry'); * @property {Number} freeCount * @property {Number} lastTime * @property {Number} maxSize - * @property {Boolean} blockSinceBump - * @property {Number} lastFeeUpdate - * @property {Rate} minRate - * @property {Rate} minReasonable * @property {Rate} minRelayFee * @emits Mempool#open * @emits Mempool#error @@ -99,7 +95,6 @@ function Mempool(options) { this.limitFree = this.options.limitFree !== false; this.limitFreeRelay = this.options.limitFreeRelay || 15; this.relayPriority = this.options.relayPriority !== false; - this.rejectFee = this.options.rejectFee === true; this.requireStandard = this.options.requireStandard != null ? this.options.requireStandard : this.network.requireStandard; @@ -110,10 +105,6 @@ function Mempool(options) { this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE; this.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY; - this.blockSinceBump = false; - this.lastFeeUpdate = utils.now(); - this.minRate = 0; - this.minReasonable = this.network.minRelay; this.minRelay = this.network.minRelay; } @@ -190,9 +181,6 @@ Mempool.prototype._addBlock = function addBlock(block) { entries.push(entry); } - this.blockSinceBump = true; - this.lastFeeUpdate = utils.now(); - if (this.fees) this.fees.processBlock(block.height, entries, this.chain.isFull()); @@ -388,7 +376,7 @@ Mempool.prototype.isSpent = function isSpent(hash, index) { }; /** - * Get an output's spender transaction. + * Get an output's spender entry. * @param {Hash} hash * @param {Number} index * @returns {MempoolEntry} @@ -398,6 +386,22 @@ Mempool.prototype.getSpent = function getSpent(hash, index) { return this.spents[hash + index]; }; +/** + * Get an output's spender transaction. + * @param {Hash} hash + * @param {Number} index + * @returns {MempoolEntry} + */ + +Mempool.prototype.getSpentTX = function getSpentTX(hash, index) { + var entry = this.spents[hash + index]; + + if (!entry) + return; + + return entry.tx; +}; + /** * Find all coins pertaining to a certain address. * @param {Address[]} addresses @@ -788,7 +792,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { continue; } - this.logger.spam('Resolved orphan %s in mempool.', orphan.tx.rhash); + this.logger.spam('Resolved orphan %s in mempool.', tx.rhash); } }); @@ -800,9 +804,10 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { */ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) { - var rate, hash; + var tx = entry.tx; + var hash; - this.removeOrphan(entry.tx); + this.removeOrphan(tx); // We do not remove spenders if this is // being removed for a block. The spenders @@ -810,66 +815,21 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) { // now exist on the blockchain). if (limit) { this.removeSpenders(entry); - this.logger.debug('Evicting %s from the mempool.', entry.tx.rhash); + this.logger.debug('Evicting %s from the mempool.', tx.rhash); + } else { + this.logger.spam('Removing block tx %s from mempool.', tx.rhash); } this.untrackEntry(entry); if (this.fees) { - hash = entry.tx.hash('hex'); + hash = tx.hash('hex'); this.fees.removeTX(hash); } - if (limit) { - this.logger.spam('Removed tx %s from mempool.', entry.tx.rhash); - rate = TX.getRate(entry.sizes, entry.fees); - rate += this.minReasonable; - if (rate > this.minRate) { - this.minRate = rate; - this.blockSinceBump = false; - } - } else { - this.logger.spam('Removed block tx %s from mempool.', entry.tx.rhash); - } - this.emit('remove entry', entry); }; -/** - * Calculate and update the minimum rolling fee rate. - * @returns {Rate} Rate. - */ - -Mempool.prototype.getMinRate = function getMinRate() { - var now, halflife, size; - - if (!this.blockSinceBump || this.minRate === 0) - return this.minRate; - - now = utils.now(); - - if (now > this.lastFeeUpdate + 10) { - halflife = constants.mempool.FEE_HALFLIFE; - size = this.getSize(); - - if (size < this.maxSize / 4) - halflife >>>= 2; - else if (size < this.maxSize / 2) - halflife >>>= 1; - - this.minRate /= Math.pow(2.0, (now - this.lastFeeUpdate) / halflife | 0); - this.minRate |= 0; - this.lastFeeUpdate = now; - - if (this.minRate < this.minReasonable / 2) { - this.minRate = 0; - return 0; - } - } - - return Math.max(this.minRate, this.minReasonable); -}; - /** * Verify a transaction with mempool standards. * @param {TX} tx @@ -877,16 +837,15 @@ Mempool.prototype.getMinRate = function getMinRate() { */ Mempool.prototype.verify = co(function* verify(entry) { + var tx = entry.tx; var height = this.chain.height + 1; var lockFlags = flags.STANDARD_LOCKTIME_FLAGS; var flags1 = flags.STANDARD_VERIFY_FLAGS; var flags2 = flags1 & ~(flags.VERIFY_WITNESS | flags.VERIFY_CLEANSTACK); var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK; var mandatory = flags.MANDATORY_VERIFY_FLAGS; - var tx = entry.tx; var ret = new VerifyResult(); - var fee, modFee, now, size, minRate; - var rejectFee, minRelayFee, count, result; + var now, minFee, count, result; result = yield this.checkLocks(tx, lockFlags); @@ -923,24 +882,9 @@ Mempool.prototype.verify = co(function* verify(entry) { 0); } - fee = tx.getFee(); - modFee = entry.fees; - size = entry.size; + minFee = tx.getMinFee(entry.size, this.minRelay); - if (this.rejectFee) { - minRate = this.getMinRate(); - rejectFee = tx.getMinFee(size, minRate); - if (rejectFee > 0 && modFee < rejectFee) { - throw new VerifyError(tx, - 'insufficientfee', - 'mempool min fee not met', - 0); - } - } - - minRelayFee = tx.getMinFee(size, this.minRelay); - - if (this.relayPriority && modFee < minRelayFee) { + if (this.relayPriority && entry.fee < minFee) { if (!entry.isFree(height)) { throw new VerifyError(tx, 'insufficientfee', @@ -954,7 +898,7 @@ Mempool.prototype.verify = co(function* verify(entry) { // sending thousands of free transactions just to be // annoying or make others' transactions take longer // to confirm. - if (this.limitFree && modFee < minRelayFee) { + if (this.limitFree && entry.fee < minFee) { now = utils.now(); // Use an exponentially decaying ~10-minute window: @@ -970,10 +914,10 @@ Mempool.prototype.verify = co(function* verify(entry) { 0); } - this.freeCount += size; + this.freeCount += entry.size; } - if (this.rejectAbsurdFees && fee > minRelayFee * 10000) + if (this.rejectAbsurdFees && entry.fee > minFee * 10000) throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); count = this.countAncestors(tx); @@ -1087,18 +1031,19 @@ Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) { */ Mempool.prototype.countAncestors = function countAncestors(tx) { - return this._countAncestors(tx, {}, 0); + return this._countAncestors(tx, 0, {}); }; /** * Traverse ancestors and count. * @private * @param {TX} tx - * @param {Object} set * @param {Number} count + * @param {Object} set + * @returns {Number} */ -Mempool.prototype._countAncestors = function countAncestors(tx, set, count) { +Mempool.prototype._countAncestors = function countAncestors(tx, count, set) { var i, input, hash, prev; for (i = 0; i < tx.inputs.length; i++) { @@ -1118,7 +1063,7 @@ Mempool.prototype._countAncestors = function countAncestors(tx, set, count) { if (count > constants.mempool.ANCESTOR_LIMIT) break; - count = this._countAncestors(prev, set, count); + count = this._countAncestors(prev, count, set); if (count > constants.mempool.ANCESTOR_LIMIT) break; @@ -1135,7 +1080,7 @@ Mempool.prototype._countAncestors = function countAncestors(tx, set, count) { */ Mempool.prototype.countDescendants = function countDescendants(tx) { - return this._countDescendants(tx, {}, 0); + return this._countDescendants(tx, 0, {}); }; /** @@ -1143,20 +1088,21 @@ Mempool.prototype.countDescendants = function countDescendants(tx) { * descendants a transaction may have. * @private * @param {TX} tx + * @param {Number} count + * @param {Object} set * @returns {Number} */ -Mempool.prototype._countDescendants = function countDescendants(tx, set, count) { +Mempool.prototype._countDescendants = function countDescendants(tx, count, set) { var hash = tx.hash('hex'); - var i, entry, next, nhash; + var i, next, nhash; for (i = 0; i < tx.outputs.length; i++) { - entry = this.getSpent(hash, i); + next = this.getSpentTX(hash, i); - if (!entry) + if (!next) continue; - next = entry.tx; nhash = next.hash('hex'); if (set[nhash]) @@ -1165,7 +1111,7 @@ Mempool.prototype._countDescendants = function countDescendants(tx, set, count) set[nhash] = true; count += 1; - count = this._countDescendants(next, set, count); + count = this._countDescendants(next, count, set); } return count; @@ -1185,6 +1131,8 @@ Mempool.prototype.getAncestors = function getAncestors(tx) { * Get all transaction ancestors. * @private * @param {TX} tx + * @param {MempoolEntry[]} entries + * @param {Object} set * @returns {MempoolEntry[]} */ @@ -1224,20 +1172,21 @@ Mempool.prototype.getDescendants = function getDescendants(tx) { /** * Get all a transaction descendants. * @param {TX} tx + * @param {MempoolEntry[]} entries + * @param {Object} set * @returns {MempoolEntry[]} */ Mempool.prototype._getDescendants = function getDescendants(tx, entries, set) { var hash = tx.hash('hex'); - var i, entry, next, nhash; + var i, next, nhash; for (i = 0; i < tx.outputs.length; i++) { - entry = this.getSpent(hash, i); + next = this.getSpentTX(hash, i); - if (!entry) + if (!next) continue; - next = entry.tx; nhash = next.hash('hex'); if (set[nhash]) diff --git a/lib/mempool/mempoolentry.js b/lib/mempool/mempoolentry.js index d2ee2efa..78b8d6b9 100644 --- a/lib/mempool/mempoolentry.js +++ b/lib/mempool/mempoolentry.js @@ -19,18 +19,12 @@ var TX = require('../primitives/tx'); * @param {Number} options.height - Entry height. * @param {Number} options.priority - Entry priority. * @param {Number} options.ts - Entry time. - * @param {Amount} options.chainValue - Value of on-chain coins. - * @param {Number} options.count - Number of descendants (includes tx). - * @param {Number} options.size - TX and descendant modified size. - * @param {Amount} options.fees - TX and descendant delta-applied fees. + * @param {Amount} options.value - Value of on-chain coins. * @property {TX} tx * @property {Number} height * @property {Number} priority * @property {Number} ts - * @property {Amount} chainValue - * @property {Number} count - * @property {Number} size - * @property {Amount} fees + * @property {Amount} value */ function MempoolEntry(options) { @@ -43,11 +37,7 @@ function MempoolEntry(options) { this.priority = 0; this.fee = 0; this.ts = 0; - - this.chainValue = 0; - this.count = 0; - this.sizes = 0; - this.fees = 0; + this.value = 0; this.dependencies = false; if (options) @@ -67,13 +57,8 @@ MempoolEntry.prototype.fromOptions = function fromOptions(options) { this.priority = options.priority; this.fee = options.fee; this.ts = options.ts; - - this.chainValue = options.chainValue; - this.count = options.count; - this.sizes = options.sizes; - this.fees = options.fees; + this.value = options.value; this.dependencies = options.dependencies; - return this; }; @@ -100,10 +85,11 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) { var dependencies = false; var size = tx.getVirtualSize(); var fee = tx.getFee(); - var i; + var i, input; for (i = 0; i < tx.inputs.length; i++) { - if (tx.inputs[i].coin.height === -1) { + input = tx.inputs[i]; + if (input.coin.height === -1) { dependencies = true; break; } @@ -114,11 +100,8 @@ MempoolEntry.prototype.fromTX = function fromTX(tx, height) { this.size = size; this.priority = priority; this.fee = fee; - this.chainValue = value; this.ts = utils.now(); - this.count = 1; - this.sizes = size; - this.fees = fee; + this.value = value; this.dependencies = dependencies; return this; @@ -146,7 +129,7 @@ MempoolEntry.fromTX = function fromTX(tx, height) { MempoolEntry.prototype.getPriority = function getPriority(height) { var heightDelta = height - this.height; var modSize = this.tx.getModifiedSize(this.size); - var deltaPriority = (heightDelta * this.chainValue) / modSize; + var deltaPriority = (heightDelta * this.value) / modSize; var result = this.priority + Math.floor(deltaPriority); if (result < 0) result = 0; diff --git a/lib/miner/miner.js b/lib/miner/miner.js index d409acaf..3daeca06 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -10,6 +10,7 @@ var utils = require('../utils/utils'); var co = require('../utils/co'); var assert = require('assert'); +var constants = require('../protocol/constants'); var AsyncObject = require('../utils/async'); var MinerBlock = require('./minerblock'); var Address = require('../primitives/address'); @@ -48,10 +49,15 @@ function Miner(options) { this.since = 0; this.version = -1; - this.address = Address(options.address); + this.addresses = []; this.coinbaseFlags = options.coinbaseFlags || 'mined by bcoin'; - this._init(); + this.minWeight = 0; + this.maxWeight = 750000 * 4; + this.priorityWeight = 50000 * 4; + this.minPriority = constants.tx.FREE_THRESHOLD; + + this._init(options); } utils.inherits(Miner, AsyncObject); @@ -61,14 +67,23 @@ utils.inherits(Miner, AsyncObject); * @private */ -Miner.prototype._init = function _init() { +Miner.prototype._init = function _init(options) { var self = this; + var i; + + if (options.address) + this.addAddress(options.address); + + if (options.addresses) { + for (i = 0; i < options.addresses.length; i++) + this.addAddress(options.addresses[i]); + } this.chain.on('tip', function(tip) { if (!self.attempt) return; - if (self.attempt.block.prevBlock !== tip.hash) + if (self.attempt.block.prevBlock === tip.prevBlock) self.attempt.destroy(); }); @@ -239,9 +254,9 @@ Miner.prototype._onStop = function _onStop() { * @returns {Promise} - Returns {@link MinerBlock}. */ -Miner.prototype.createBlock = co(function* createBlock(tip) { +Miner.prototype.createBlock = co(function* createBlock(tip, address) { var version = this.version; - var ts, attempt, target, entries; + var ts, attempt, target, locktime; if (!tip) tip = this.chain.tip; @@ -249,26 +264,32 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { assert(tip); ts = Math.max(time.now(), tip.ts + 1); + locktime = ts; target = yield this.chain.getTargetAsync(ts, tip); if (version === -1) version = yield this.chain.computeBlockVersion(tip); + if (this.chain.state.hasMTP()) + locktime = yield tip.getMedianTimeAsync(); + + if (!address) + address = this.getAddress(); + attempt = new MinerBlock({ tip: tip, version: version, bits: target, + locktime: locktime, flags: this.chain.state.flags, - address: this.address, + address: address, coinbaseFlags: this.coinbaseFlags, witness: this.chain.state.hasWitness(), network: this.network }); - entries = this.getSorted(); - - attempt.build(entries); + this.fill(attempt); return attempt; }); @@ -279,26 +300,11 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { * @returns {Promise} - Returns [{@link Block}]. */ -Miner.prototype.mineBlock = co(function* mineBlock(tip) { - var attempt = yield this.createBlock(tip); +Miner.prototype.mineBlock = co(function* mineBlock(tip, address) { + var attempt = yield this.createBlock(tip, address); return yield attempt.mineAsync(); }); -/** - * Add a transaction to the current block. - * @param {TX} tx - */ - -Miner.prototype.addTX = function addTX(tx) { - if (!this.running) - return; - - if (!this.attempt) - return; - - this.attempt.addTX(tx); -}; - /** * Notify the miner that a new tx has entered the mempool. * @param {MempoolEntry} entry @@ -317,18 +323,37 @@ Miner.prototype.notifyEntry = function notifyEntry() { } }; +/** + * Add an address to the address list. + * @param {Address} address + */ + +Miner.prototype.addAddress = function addAddress(address) { + this.addresses.push(Address(address)); +}; + +/** + * Get a random address from the address list. + * @returns {Address} + */ + +Miner.prototype.getAddress = function getAddress() { + assert(this.addresses.length !== 0, 'No address passed in for miner.'); + return this.addresses[Math.random() * this.addresses.length | 0]; +}; + /** * Get mempool entries, sort by dependency order. * @returns {MempoolEntry[]} */ -Miner.prototype.getSorted = function getSorted() { +Miner.prototype.fill = function fill(attempt) { var depMap = {}; - var count = {}; - var result = []; - var top = []; - var i, j, entry, tx, hash, input; - var prev, hasDeps, deps, hashes; + var block = attempt.block; + var queue = new Queue(cmpPriority); + var priority = true; + var i, j, entry, item, tx, hash, input; + var prev, deps, hashes, weight, sigops; if (!this.mempool) return []; @@ -338,10 +363,17 @@ Miner.prototype.getSorted = function getSorted() { for (i = 0; i < hashes.length; i++) { hash = hashes[i]; entry = this.mempool.getEntry(hash); - tx = entry.tx; + item = new QueueItem(entry, attempt); + tx = item.tx; - count[hash] = 0; - hasDeps = false; + if (tx.isCoinbase()) + throw new Error('Cannot add coinbase to block.'); + + if (!tx.hasCoins()) + throw new Error('Cannot add empty tx to block.'); + + if (!tx.isFinal(attempt.height, attempt.locktime)) + continue; for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; @@ -350,27 +382,57 @@ Miner.prototype.getSorted = function getSorted() { if (!this.mempool.hasTX(prev)) continue; - hasDeps = true; + item.depCount += 1; if (!depMap[prev]) depMap[prev] = []; - depMap[prev].push(entry); - count[hash]++; + depMap[prev].push(item); } - if (hasDeps) + if (item.depCount > 0) continue; - top.push(entry); + queue.push(item); } - for (i = 0; i < top.length; i++) { - entry = top[i]; - tx = entry.tx; - hash = tx.hash('hex'); + while (queue.size() > 0) { + item = queue.pop(); + tx = item.tx; + hash = item.hash; + weight = attempt.weight; + sigops = attempt.sigops; - result.push(entry); + if (!attempt.witness && tx.hasWitness()) + continue; + + weight += tx.getWeight(); + + if (weight > this.maxWeight) + continue; + + sigops += tx.getSigopsWeight(attempt.flags); + + if (sigops > constants.block.MAX_SIGOPS_WEIGHT) + continue; + + if (priority) { + if (weight > this.priorityWeight || item.priority < this.minPriority) { + queue.cmp = cmpRate; + priority = false; + queue.push(item); + continue; + } + } else { + if (item.free && weight >= this.minWeight) + continue; + } + + attempt.weight = weight; + attempt.sigops = sigops; + attempt.fees += item.fee; + + block.txs.push(tx.clone()); deps = depMap[hash]; @@ -378,18 +440,67 @@ Miner.prototype.getSorted = function getSorted() { continue; for (j = 0; j < deps.length; j++) { - entry = deps[j]; - tx = entry.tx; - hash = tx.hash('hex'); - - if (--count[hash] === 0) - top.push(entry); + item = deps[j]; + if (--item.depCount === 0) + queue.push(item); } } - return result; + attempt.updateCoinbase(); + attempt.updateMerkle(); + + assert(block.getWeight() <= attempt.weight); }; +/** + * QueueItem + * @constructor + */ + +function QueueItem(entry, attempt) { + this.tx = entry.tx; + this.hash = entry.tx.hash('hex'); + this.fee = entry.getFee(); + this.rate = entry.getRate(); + this.priority = entry.getPriority(attempt.height); + this.free = entry.isFree(attempt.height); + this.depCount = 0; +} + +/** + * Queue + * @constructor + */ + +function Queue(cmp) { + this.cmp = cmp; + this.items = []; +} + +Queue.prototype.size = function size() { + return this.items.length; +}; + +Queue.prototype.push = function push(item) { + utils.binaryInsert(this.items, item, this.cmp); +}; + +Queue.prototype.pop = function pop() { + return this.items.pop(); +}; + +/* + * Helpers + */ + +function cmpPriority(a, b) { + return a.priority - b.priority; +} + +function cmpRate(a, b) { + return a.rate - b.rate; +} + /* * Expose */ diff --git a/lib/miner/minerblock.js b/lib/miner/minerblock.js index 8ff2e0db..53395305 100644 --- a/lib/miner/minerblock.js +++ b/lib/miner/minerblock.js @@ -53,6 +53,7 @@ function MinerBlock(options) { this.height = options.tip.height + 1; this.bits = options.bits; this.target = utils.fromCompact(this.bits).toArrayLike(Buffer, 'le', 32); + this.locktime = options.locktime; this.flags = options.flags; this.extraNonce = new BN(0); this.iterations = 0; @@ -61,9 +62,11 @@ function MinerBlock(options) { this.address = options.address; this.network = Network.get(options.network); this.destroyed = false; + this.reward = Block.reward(this.height, this.network); this.sigops = 0; this.weight = 0; + this.fees = 0; this.coinbase = new TX(); this.coinbase.mutable = true; @@ -98,6 +101,7 @@ MinerBlock.prototype.__defineGetter__('rate', function() { */ MinerBlock.prototype._init = function _init() { + var scale = constants.WITNESS_SCALE_FACTOR; var block = this.block; var cb = this.coinbase; var input, output, hash, witnessNonce; @@ -158,13 +162,25 @@ MinerBlock.prototype._init = function _init() { block.nonce = 0; block.height = this.height; - block.addTX(cb); + block.txs.push(cb); // Update coinbase since our coinbase was added. this.updateCoinbase(); // Create our merkle root. this.updateMerkle(); + + // Initialize weight. + this.weight = this.block.getWeight(); + + // 4 extra bytes for varint tx count. + this.weight += 4 * scale; + + // 8 extra bytes for extra nonce. + this.weight += 8 * scale; + + // Initialize sigops weight. + this.sigops = cb.getSigopsWeight(this.flags); }; /** @@ -197,7 +213,7 @@ MinerBlock.prototype.updateCoinbase = function updateCoinbase() { input.script.compile(); // Update reward. - output.value = this.block.getReward(this.network); + output.value = this.reward + this.fees; }; /** @@ -255,6 +271,9 @@ MinerBlock.prototype.addTX = function addTX(tx) { weight = tx.getWeight(); sigops = tx.getSigopsWeight(this.flags); + if (!tx.isFinal(this.height, this.locktime)) + return false; + if (this.weight + weight > constants.block.MAX_WEIGHT) return false; @@ -264,6 +283,10 @@ MinerBlock.prototype.addTX = function addTX(tx) { if (!this.witness && tx.hasWitness()) return false; + this.weight += weight; + this.sigops += sigops; + this.fees += tx.getFee(); + // Add the tx to our block this.block.addTX(tx.clone()); @@ -276,47 +299,6 @@ MinerBlock.prototype.addTX = function addTX(tx) { return true; }; -/** - * Fill the block with sorted mempool entries. - * @param {MempoolEntry[]} entries - */ - -MinerBlock.prototype.build = function build(entries) { - var len = Math.min(entries.length, constants.block.MAX_SIZE); - var i, entry, tx, weight, sigops; - - this.weight = this.block.getWeight() + 28; - this.sigops = this.coinbase.getSigopsWeight(this.flags); - - for (i = 0; i < len; i++) { - entry = entries[i]; - tx = entry.tx; - - weight = tx.getWeight(); - sigops = tx.getSigopsWeight(this.flags); - - if (this.weight + weight > constants.block.MAX_WEIGHT) - break; - - if (this.sigops + sigops > constants.block.MAX_SIGOPS_WEIGHT) - break; - - this.weight += weight; - this.sigops += sigops; - - // Add the tx to our block - this.block.addTX(tx.clone()); - } - - // Update coinbase value - this.updateCoinbase(); - - // Update merkle root for new coinbase and new tx - this.updateMerkle(); - - assert(this.block.getWeight() <= this.weight); -}; - /** * Hash until the nonce overflows. * @returns {Boolean} Whether the nonce was found. diff --git a/lib/node/node.js b/lib/node/node.js index d5379724..b110c832 100644 --- a/lib/node/node.js +++ b/lib/node/node.js @@ -252,12 +252,8 @@ Node.prototype.openWallet = co(function* openWallet() { 'Loaded wallet with id=%s wid=%d address=%s', wallet.id, wallet.wid, wallet.getAddress()); - // Set the miner payout address if the - // programmer didn't pass one in. - if (this.miner) { - if (!this.options.payoutAddress) - this.miner.address = wallet.getAddress(); - } + if (this.miner) + this.miner.addAddress(wallet.getAddress()); this.wallet = wallet; }); diff --git a/lib/primitives/tx.js b/lib/primitives/tx.js index ab84fe44..f6c6f23c 100644 --- a/lib/primitives/tx.js +++ b/lib/primitives/tx.js @@ -2081,6 +2081,82 @@ TX.getRate = function getRate(size, fee) { return Math.floor(fee * 1000 / size); }; +/** + * Sort an array of transactions in dependency order. + * @param {TX[]} txs + * @returns {TX[]} + */ + +TX.sort = function sort(txs) { + var depMap = {}; + var count = {}; + var result = []; + var top = []; + var map = txs; + var i, j, tx, hash, input; + var prev, hasDeps, deps; + + if (Array.isArray(txs)) { + map = {}; + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + hash = tx.hash('hex'); + map[hash] = tx; + } + } + + for (i = 0; i < txs.length; i++) { + tx = txs[i]; + hash = tx.hash('hex'); + hasDeps = false; + + count[hash] = 0; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + prev = input.prevout.hash; + + if (!map[prev]) + continue; + + count[hash] += 1; + hasDeps = true; + + if (!depMap[prev]) + depMap[prev] = []; + + depMap[prev].push(tx); + } + + if (hasDeps) + continue; + + top.push(tx); + } + + for (i = 0; i < top.length; i++) { + tx = top[i]; + hash = tx.hash('hex'); + + result.push(tx); + + deps = depMap[hash]; + + if (!deps) + continue; + + for (j = 0; j < deps.length; j++) { + tx = deps[j]; + hash = tx.hash('hex'); + + if (--count[hash] === 0) + top.push(tx); + } + } + + return result; +}; + /** * Inspect the transaction and return a more * user-friendly representation of the data. diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 878917a0..66e3a505 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -20,6 +20,7 @@ var BufferWriter = require('../utils/writer'); var TXDB = require('./txdb'); var Path = require('./path'); var Address = require('../primitives/address'); +var TX = require('../primitives/tx'); var MTX = require('../primitives/mtx'); var WalletKey = require('./walletkey'); var HD = require('../hd/hd'); @@ -1650,6 +1651,8 @@ Wallet.prototype.resend = co(function* resend() { if (txs.length > 0) this.logger.info('Rebroadcasting %d transactions.', txs.length); + txs = TX.sort(txs); + for (i = 0; i < txs.length; i++) yield this.db.send(txs[i]); diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 06c24781..f377b3b4 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -1403,6 +1403,7 @@ WalletDB.prototype.getPendingTX = co(function* getPendingTX() { WalletDB.prototype.resend = co(function* resend() { var keys = yield this.getPendingTX(); + var txs = []; var i, key, data, tx; if (keys.length > 0) @@ -1420,8 +1421,13 @@ WalletDB.prototype.resend = co(function* resend() { if (tx.isCoinbase()) continue; - yield this.send(tx); + txs.push(tx); } + + txs = TX.sort(txs); + + for (i = 0; i < txs.length; i++) + yield this.send(txs[i]); }); /** diff --git a/test/chain-test.js b/test/chain-test.js index b30a0ee3..c55671c6 100644 --- a/test/chain-test.js +++ b/test/chain-test.js @@ -79,7 +79,8 @@ describe('Chain', function() { it('should open walletdb', cob(function* () { wallet = yield walletdb.create(); - miner.address = wallet.getAddress(); + miner.addresses.length = 0; + miner.addAddress(wallet.getAddress()); })); it('should mine a block', cob(function* () {