From 12a5bcc4e2bffea593924300aa4093f9fb976610 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 6 Oct 2016 00:08:23 -0700 Subject: [PATCH] miner: better sorting of txs. --- lib/mempool/mempool.js | 4 +- lib/miner/miner.js | 145 ++++++++++++++++++++++++++++++++-------- lib/miner/minerblock.js | 68 +++++++++++++++++-- lib/node/fullnode.js | 2 +- 4 files changed, 183 insertions(+), 36 deletions(-) diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 930a27ef..afad0378 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -728,7 +728,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { this.trackEntry(entry); this.emit('tx', entry.tx); - this.emit('add tx', entry.tx); + this.emit('add entry', entry); if (this.fees) this.fees.processTX(entry, this.chain.isFull()); @@ -809,7 +809,7 @@ Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) { this.logger.spam('Removed block tx %s from mempool.', entry.tx.rhash); } - this.emit('remove tx', entry.tx); + this.emit('remove entry', entry); }; /** diff --git a/lib/miner/miner.js b/lib/miner/miner.js index 355cf71d..e6aaf4f1 100644 --- a/lib/miner/miner.js +++ b/lib/miner/miner.js @@ -121,25 +121,30 @@ Miner.prototype.start = co(function* start() { this.running = true; - try { - attempt = yield this.createBlock(); - } catch (e) { - this.running = false; - this.emit('error', e); - return; - } + while (!block) { + if (!this.running) + return; - if (!this.running) - return; + try { + attempt = yield this.createBlock(); + } catch (e) { + this.running = false; + this.emit('error', e); + return; + } - this.attempt = attempt; + if (!this.running) + return; - try { - block = yield attempt.mineAsync(); - } catch (e) { - this.emit('error', e); - this.restart(); - return; + this.attempt = attempt; + + try { + block = yield attempt.mineAsync(); + } catch (e) { + this.emit('error', e); + this.restart(); + return; + } } try { @@ -193,7 +198,7 @@ Miner.prototype.restart = function restart() { Miner.prototype.createBlock = co(function* createBlock(tip) { var version = this.version; - var i, ts, attempt, txs, tx, target; + var ts, attempt, target, entries; if (!tip) tip = this.chain.tip; @@ -211,21 +216,16 @@ Miner.prototype.createBlock = co(function* createBlock(tip) { tip: tip, version: version, bits: target, + flags: this.chain.state.flags, address: this.address, coinbaseFlags: this.coinbaseFlags, witness: this.chain.state.hasWitness(), network: this.network }); - if (!this.mempool) - return attempt; + entries = this.getSorted(); - txs = this.mempool.getHistory(); - - for (i = 0; i < txs.length; i++) { - tx = txs[i]; - attempt.addTX(tx); - } + attempt.build(entries); return attempt; }); @@ -250,8 +250,99 @@ Miner.prototype.addTX = function addTX(tx) { if (!this.running) return; - if (this.attempt) - this.attempt.addTX(tx.clone()); + if (!this.attempt) + return; + + this.attempt.addTX(tx); +}; + +/** + * Notify the miner that a new tx has entered the mempool. + * @param {MempoolEntry} entry + */ + +Miner.prototype.notifyEntry = function notifyEntry() { + if (!this.running) + return; + + if (!this.attempt) + return; + + this.attempt.since++; +}; + +/** + * Create a block "attempt". + * @param {ChainEntry} tip + * @returns {Promise} - Returns {@link MinerBlock}. + */ + +Miner.prototype.getSorted = function getSorted() { + var depMap = {}; + var count = {}; + var result = []; + var top = []; + var i, j, entry, tx, hash, input; + var prev, hasDeps, deps, hashes; + + if (!this.mempool) + return []; + + hashes = this.mempool.getSnapshot(); + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + entry = this.mempool.getEntry(hash); + tx = entry.tx; + + count[hash] = 0; + hasDeps = false; + + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + prev = input.prevout.hash; + + if (!this.mempool.hasTX(prev)) + continue; + + hasDeps = true; + + if (!depMap[prev]) + depMap[prev] = []; + + depMap[prev].push(entry); + count[hash]++; + } + + if (hasDeps) + continue; + + top.push(entry); + } + + for (i = 0; i < top.length; i++) { + entry = top[i]; + tx = entry.tx; + hash = tx.hash('hex'); + + result.push(entry); + + deps = depMap[hash]; + + if (!deps) + continue; + + for (j = 0; j < deps.length; j++) { + entry = deps[j]; + tx = entry.tx; + hash = tx.hash('hex'); + + if (--count[hash] === 0) + result.push(entry); + } + } + + return result; }; /* diff --git a/lib/miner/minerblock.js b/lib/miner/minerblock.js index 25b1c7af..3464344e 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.flags = options.flags; this.extraNonce = new bn(0); this.iterations = 0; this.coinbaseFlags = options.coinbaseFlags; @@ -60,6 +61,10 @@ function MinerBlock(options) { this.address = options.address; this.network = Network.get(options.network); this.destroyed = false; + this.since = 0; + + this.sigops = 0; + this.weight = 0; this.coinbase = new TX(); this.coinbase.mutable = true; @@ -241,23 +246,27 @@ MinerBlock.prototype.updateMerkle = function updateMerkle() { */ MinerBlock.prototype.addTX = function addTX(tx) { - var weight; + var weight, sigops; assert(!tx.mutable, 'Cannot add mutable TX to block.'); - weight = this.block.getWeight() + tx.getWeight(); - - if (weight > constants.block.MAX_WEIGHT) + if (this.block.hasTX(tx)) return false; - if (this.block.hasTX(tx)) + weight = tx.getWeight(); + sigops = tx.getSigopsWeight(this.flags); + + if (this.weight + weight > constants.block.MAX_WEIGHT) + return false; + + if (this.sigops + sigops > constants.block.MAX_SIGOPS_WEIGHT) return false; if (!this.witness && tx.hasWitness()) return false; // Add the tx to our block - this.block.addTX(tx); + this.block.addTX(tx.clone()); // Update coinbase value this.updateCoinbase(); @@ -268,6 +277,47 @@ 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() + 12; + 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. @@ -322,6 +372,9 @@ MinerBlock.prototype.findNonceAsync = co(function* findNonceAsync() { if (nonce !== -1) break; + if (this.since > 20) + return nonce; + block.nonce = max; min += interval; @@ -381,6 +434,9 @@ MinerBlock.prototype.mineAsync = co(function* mineAsync() { if (nonce !== -1) break; + if (this.since > 20) + return; + this.iterate(); } diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 2767940f..7fc60111 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -199,7 +199,7 @@ Fullnode.prototype._init = function _init() { this.mempool.on('tx', function(tx) { self.emit('tx', tx); self.walletdb.addTX(tx).catch(onError); - self.miner.addTX(tx); + self.miner.notifyEntry(); }); this.chain.on('block', function(block) {