From c300df73405ca8f9d0735856fabdc3066f70d7de Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 7 Dec 2016 18:38:56 -0800 Subject: [PATCH] mempool: refactor. --- lib/mempool/mempool.js | 120 +++++++++++++++++++++++------------------ lib/node/fullnode.js | 4 +- 2 files changed, 70 insertions(+), 54 deletions(-) diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index 370c7ba1..83d0c8a2 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -140,14 +140,15 @@ Mempool.prototype._close = function close() { * Notify the mempool that a new block has come * in (removes all transactions contained in the * block from the mempool). - * @param {Block} block + * @param {ChainEntry} block + * @param {TX[]} txs * @returns {Promise} */ -Mempool.prototype.addBlock = co(function* addBlock(block) { +Mempool.prototype.addBlock = co(function* addBlock(block, txs) { var unlock = yield this.locker.lock(); try { - return this._addBlock(block); + return this._addBlock(block, txs); } finally { unlock(); } @@ -157,16 +158,17 @@ Mempool.prototype.addBlock = co(function* addBlock(block) { * Notify the mempool that a new block * has come without a lock. * @private - * @param {Block} block + * @param {ChainEntry} block + * @param {TX[]} txs * @returns {Promise} */ -Mempool.prototype._addBlock = function addBlock(block) { +Mempool.prototype._addBlock = function addBlock(block, txs) { var entries = []; var i, entry, tx, hash; - for (i = block.txs.length - 1; i >= 1; i--) { - tx = block.txs[i]; + for (i = txs.length - 1; i >= 1; i--) { + tx = txs[i]; hash = tx.hash('hex'); entry = this.getEntry(hash); @@ -175,7 +177,7 @@ Mempool.prototype._addBlock = function addBlock(block) { continue; } - this.removeUnchecked(entry); + this.removeEntry(entry); this.emit('confirmed', tx, block); entries.push(entry); @@ -192,14 +194,15 @@ Mempool.prototype._addBlock = function addBlock(block) { /** * Notify the mempool that a block has been disconnected * from the main chain (reinserts transactions into the mempool). - * @param {Block} block + * @param {ChainEntry} block + * @param {TX[]} txs * @returns {Promise} */ -Mempool.prototype.removeBlock = co(function* removeBlock(block) { +Mempool.prototype.removeBlock = co(function* removeBlock(block, txs) { var unlock = yield this.locker.lock(); try { - return this._removeBlock(block); + return this._removeBlock(block, txs); } finally { unlock(); } @@ -209,15 +212,16 @@ Mempool.prototype.removeBlock = co(function* removeBlock(block) { * Notify the mempool that a block * has been disconnected without a lock. * @private - * @param {Block} block + * @param {ChainEntry} block + * @param {TX[]} txs * @returns {Promise} */ -Mempool.prototype._removeBlock = function removeBlock(block) { +Mempool.prototype._removeBlock = function removeBlock(block, txs) { var i, entry, tx, hash; - for (i = 1; i < block.txs.length; i++) { - tx = block.txs[i]; + for (i = 1; i < txs.length; i++) { + tx = txs[i]; hash = tx.hash('hex'); if (this.hasTX(hash)) @@ -307,7 +311,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(entryHash) { if (!trimmed && hash === entryHash) trimmed = true; - this.removeUnchecked(entry, true); + this.removeEntry(entry, true); if (this.getSize() <= this.maxSize) return trimmed; @@ -325,7 +329,7 @@ Mempool.prototype.limitMempoolSize = function limitMempoolSize(entryHash) { if (!trimmed && hash === entryHash) trimmed = true; - this.removeUnchecked(entry, true); + this.removeEntry(entry, true); if (this.getSize() <= this.maxSize) return trimmed; @@ -594,6 +598,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); + // Basic sanity checks. + // This is important because it ensures + // other functions will be overflow safe. if (!tx.isSane(ret)) { throw new VerifyError(tx, 'invalid', @@ -601,6 +608,8 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { ret.score); } + // Coinbases are an insta-ban. + // Why? Who knows. if (tx.isCoinbase()) { throw new VerifyError(tx, 'invalid', @@ -608,6 +617,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 100); } + // Do not allow CSV until it's activated. if (this.requireStandard) { if (!this.chain.state.hasCSV() && tx.version >= 2) { throw new VerifyError(tx, @@ -617,6 +627,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { } } + // Do not allow segwit until it's activated. if (!this.chain.state.hasWitness() && !this.prematureWitness) { if (tx.hasWitness()) { throw new VerifyError(tx, @@ -626,6 +637,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { } } + // Non-contextual standardness checks. if (this.requireStandard) { if (!tx.isStandard(ret)) { throw new VerifyError(tx, @@ -643,6 +655,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { } } + // Verify transaction finality (see isFinal()). if (!(yield this.verifyFinal(tx, lockFlags))) { throw new VerifyError(tx, 'nonstandard', @@ -650,6 +663,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } + // We can maybe ignore this. if (this.exists(hash)) { throw new VerifyError(tx, 'alreadyknown', @@ -657,6 +671,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } + // We can test whether this is an + // non-fully-spent transaction on + // the chain. if (yield this.chain.db.hasCoins(hash)) { throw new VerifyError(tx, 'alreadyknown', @@ -664,6 +681,9 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } + // Quick and dirty test to verify we're + // not double-spending an output in the + // mempool. if (this.isDoubleSpend(tx)) { throw new VerifyError(tx, 'duplicate', @@ -671,23 +691,30 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } - // if (!(yield this.fillAllCoins(tx))) - // return this.maybeStoreOrphan(tx); - + // Get coin viewpoint as it + // pertains to the mempool. view = yield this.getCoinView(tx); + + // Find missing outpoints. missing = this.injectCoins(tx, view); + // Maybe store as an orphan. if (missing.length > 0) { if (this.storeOrphan(tx, missing)) return missing; return; } + // Create a mempool entry and + // begin contextual verification. entry = MempoolEntry.fromTX(tx, this.chain.height); yield this.verify(entry); - yield this._addUnchecked(entry); + // Add and index the entry. + yield this.addEntry(entry); + + // Trim size if we're too big. if (this.limitMempoolSize(hash)) { throw new VerifyError(tx, 'insufficientfee', @@ -706,23 +733,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { * @returns {Promise} */ -Mempool.prototype.addUnchecked = co(function* addUnchecked(entry) { - var unlock = yield this.locker.lock(); - try { - return yield this._addUnchecked(entry); - } finally { - unlock(); - } -}); - -/** - * Add a transaction to the mempool without a lock. - * @private - * @param {MempoolEntry} entry - * @returns {Promise} - */ - -Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { +Mempool.prototype.addEntry = co(function* addEntry(entry) { var i, resolved, tx; this.trackEntry(entry); @@ -757,7 +768,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { continue; } - this.logger.spam('Resolved orphan %s in mempool.', tx.txid()); + this.logger.debug('Resolved orphan %s in mempool.', tx.txid()); } }); @@ -768,7 +779,7 @@ Mempool.prototype._addUnchecked = co(function* addUnchecked(entry) { * @param {Boolean} limit */ -Mempool.prototype.removeUnchecked = function removeUnchecked(entry, limit) { +Mempool.prototype.removeEntry = function removeEntry(entry, limit) { var tx = entry.tx; var hash = tx.hash('hex'); @@ -810,6 +821,7 @@ Mempool.prototype.verify = co(function* verify(entry) { var ret = new VerifyResult(); var now, minFee, result; + // Verify sequence locks. if (!(yield this.verifyLocks(tx, lockFlags))) { throw new VerifyError(tx, 'nonstandard', @@ -817,6 +829,7 @@ Mempool.prototype.verify = co(function* verify(entry) { 0); } + // Check input an witness standardness. if (this.requireStandard) { if (!tx.hasStandardInputs()) { throw new VerifyError(tx, @@ -836,6 +849,7 @@ Mempool.prototype.verify = co(function* verify(entry) { } } + // Annoying process known as sigops counting. if (tx.getSigopsWeight(flags) > constants.tx.MAX_SIGOPS_WEIGHT) { throw new VerifyError(tx, 'nonstandard', @@ -843,6 +857,7 @@ Mempool.prototype.verify = co(function* verify(entry) { 0); } + // Make sure this guy gave a decent fee. minFee = tx.getMinFee(entry.size, this.minRelay); if (this.relayPriority && entry.fee < minFee) { @@ -855,19 +870,16 @@ Mempool.prototype.verify = co(function* verify(entry) { } // Continuously rate-limit free (really, very-low-fee) - // transactions. This mitigates 'penny-flooding'. i.e. - // sending thousands of free transactions just to be - // annoying or make others' transactions take longer - // to confirm. + // transactions. This mitigates 'penny-flooding'. if (this.limitFree && entry.fee < minFee) { now = util.now(); - // Use an exponentially decaying ~10-minute window: + // Use an exponentially decaying ~10-minute window. this.freeCount *= Math.pow(1 - 1 / 600, now - this.lastTime); this.lastTime = now; // The limitFreeRelay unit is thousand-bytes-per-minute - // At default rate it would take over a month to fill 1GB + // At default rate it would take over a month to fill 1GB. if (this.freeCount > this.limitFreeRelay * 10 * 1000) { throw new VerifyError(tx, 'insufficientfee', @@ -878,9 +890,11 @@ Mempool.prototype.verify = co(function* verify(entry) { this.freeCount += entry.size; } + // Important safety feature. if (this.rejectAbsurdFees && entry.fee > minFee * 10000) throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); + // Why do we have this here? Nested transactions are cool. if (this.countAncestors(tx) > this.maxAncestors) { throw new VerifyError(tx, 'nonstandard', @@ -888,10 +902,11 @@ Mempool.prototype.verify = co(function* verify(entry) { 0); } + // Contextual sanity checks. if (!tx.checkInputs(height, ret)) throw new VerifyError(tx, 'invalid', ret.reason, ret.score); - // Standard verification + // Script verification. try { yield this.verifyInputs(tx, flags1); } catch (error) { @@ -1405,8 +1420,8 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx) { continue; if (--orphan.missing === 0) { - this.totalOrphans--; delete this.orphans[orphanHash]; + this.totalOrphans--; try { resolved.push(orphan.toTX()); } catch (e) { @@ -1438,9 +1453,11 @@ Mempool.prototype.removeOrphan = function removeOrphan(hash) { tx = orphan.toTX(); } catch (e) { delete this.orphans[hash]; + this.totalOrphans--; this.logger.warning('%s %s', 'Warning: possible memory corruption.', 'Orphan failed deserialization.'); + return; } prevout = tx.getPrevout(); @@ -1465,10 +1482,9 @@ Mempool.prototype.removeOrphan = function removeOrphan(hash) { } delete this.orphans[hash]; + this.totalOrphans--; this.emit('remove orphan', tx); - - this.totalOrphans--; }; /** @@ -1585,7 +1601,7 @@ Mempool.prototype.fillAllHistory = function fillAllHistory(tx) { if (tx.hasCoins()) return Promise.resolve(tx); - return this.chain.db.fillCoins(tx); + return this.chain.db.fillHistory(tx); }; /** @@ -1809,7 +1825,7 @@ Mempool.prototype.removeSpenders = function removeSpenders(entry) { if (!spender) continue; - this.removeUnchecked(spender, true); + this.removeEntry(spender, true); } }; diff --git a/lib/node/fullnode.js b/lib/node/fullnode.js index 40d22afe..c9da091d 100644 --- a/lib/node/fullnode.js +++ b/lib/node/fullnode.js @@ -208,14 +208,14 @@ FullNode.prototype._init = function _init() { self.emit('connect', entry, block); if (self.chain.synced) - self.mempool.addBlock(block).catch(onError); + self.mempool.addBlock(entry, block.txs).catch(onError); }); this.chain.on('disconnect', function(entry, block) { self.emit('disconnect', entry, block); if (self.chain.synced) - self.mempool.removeBlock(block).catch(onError); + self.mempool.removeBlock(entry, block.txs).catch(onError); }); this.chain.on('reset', function(tip) {