From 1269aab7a500daf046297e83ecc2e403dc2dcffe Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 7 Dec 2016 17:39:27 -0800 Subject: [PATCH] mempool: use coin viewpoint. --- lib/blockchain/chain.js | 2 +- lib/blockchain/coinview.js | 27 ++++++ lib/mempool/mempool.js | 169 ++++++++++++++++++++++++++++--------- lib/protocol/constants.js | 8 +- 4 files changed, 156 insertions(+), 50 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index bfe3d3fd..32c06b04 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -2229,7 +2229,7 @@ Chain.prototype.getDeploymentState = co(function* getDeploymentState() { * @returns {Promise} - Returns Boolean. */ -Chain.prototype.checkFinal = co(function* checkFinal(prev, tx, flags) { +Chain.prototype.verifyFinal = co(function* verifyFinal(prev, tx, flags) { var height = prev.height + 1; var ts; diff --git a/lib/blockchain/coinview.js b/lib/blockchain/coinview.js index 215e28fd..ff8696b9 100644 --- a/lib/blockchain/coinview.js +++ b/lib/blockchain/coinview.js @@ -36,6 +36,16 @@ CoinView.prototype.get = function get(hash) { return this.unspent[hash]; }; +/** + * Test whether the view has an entry. + * @param {Hash} hash + * @returns {Boolean} + */ + +CoinView.prototype.has = function has(hash) { + return this.unspent[hash] != null; +}; + /** * Add coins to the collection. * @param {Coins} coins @@ -95,6 +105,23 @@ CoinView.prototype.spendFrom = function spendFrom(coins, index) { return entry.toCoin(coins, index); }; +/** + * Get a single coin. + * @param {Hash} hash + * @param {Number} index + * @returns {Coin} + */ + +CoinView.prototype.getCoin = function getCoin(hash, index) { + var coins = this.get(hash); + var entry; + + if (!coins) + return; + + return coins.getCoin(index); +}; + /** * Retrieve coins from database. * @param {TX} tx diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index d413ea41..06a8f73c 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -106,6 +106,8 @@ function Mempool(options) { this.replaceByFee = !!this.options.replaceByFee; this.maxSize = options.maxSize || constants.mempool.MAX_MEMPOOL_SIZE; + this.maxOrphans = options.maxOrphans || constants.mempool.MAX_ORPHAN_TX; + this.maxAncestors = options.maxAncestors || constants.mempool.ANCESTOR_LIMIT; this.expiryTime = options.expiryTime || constants.mempool.MEMPOOL_EXPIRY; this.minRelay = this.network.minRelay; } @@ -340,7 +342,7 @@ Mempool.prototype.limitOrphans = function limitOrphans() { var orphans = Object.keys(this.orphans); var i, hash; - while (this.totalOrphans > constants.mempool.MAX_ORPHAN_TX) { + while (this.totalOrphans > this.maxOrphans) { i = crypto.randomRange(0, orphans.length); hash = orphans[i]; orphans.splice(i, 1); @@ -587,19 +589,11 @@ Mempool.prototype.addTX = co(function* addTX(tx) { Mempool.prototype._addTX = co(function* _addTX(tx) { var lockFlags = constants.flags.STANDARD_LOCKTIME_FLAGS; var hash = tx.hash('hex'); - var ret, entry, result, exists; + var ret = new VerifyResult(); + var entry, view, missing; assert(!tx.mutable, 'Cannot add mutable TX to mempool.'); - ret = new VerifyResult(); - - if (tx.ts !== 0) { - throw new VerifyError(tx, - 'alreadyknown', - 'txn-already-known', - 0); - } - if (!tx.isSane(ret)) { throw new VerifyError(tx, 'invalid', @@ -649,9 +643,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { } } - result = yield this.chain.checkFinal(this.chain.tip, tx, lockFlags); - - if (!result) { + if (!(yield this.verifyFinal(tx, lockFlags))) { throw new VerifyError(tx, 'nonstandard', 'non-final', @@ -665,9 +657,7 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } - exists = yield this.chain.db.hasCoins(hash); - - if (exists) { + if (yield this.chain.db.hasCoins(hash)) { throw new VerifyError(tx, 'alreadyknown', 'txn-already-known', @@ -681,8 +671,17 @@ Mempool.prototype._addTX = co(function* _addTX(tx) { 0); } - if (!(yield this.fillAllCoins(tx))) - return this.storeOrphan(tx); + // if (!(yield this.fillAllCoins(tx))) + // return this.maybeStoreOrphan(tx); + + view = yield this.getCoinView(tx); + missing = this.injectCoins(tx, view); + + if (missing.length > 0) { + if (this.storeOrphan(tx, missing)) + return missing; + return; + } entry = MempoolEntry.fromTX(tx, this.chain.height); @@ -809,11 +808,9 @@ Mempool.prototype.verify = co(function* verify(entry) { var flags3 = flags1 & ~flags.VERIFY_CLEANSTACK; var mandatory = flags.MANDATORY_VERIFY_FLAGS; var ret = new VerifyResult(); - var now, minFee, count, result; + var now, minFee, result; - result = yield this.verifyLocks(tx, lockFlags); - - if (!result) { + if (!(yield this.verifyLocks(tx, lockFlags))) { throw new VerifyError(tx, 'nonstandard', 'non-BIP68-final', @@ -884,9 +881,7 @@ Mempool.prototype.verify = co(function* verify(entry) { if (this.rejectAbsurdFees && entry.fee > minFee * 10000) throw new VerifyError(tx, 'highfee', 'absurdly-high-fee', 0); - count = this.countAncestors(tx); - - if (count > constants.mempool.ANCESTOR_LIMIT) { + if (this.countAncestors(tx) > this.maxAncestors) { throw new VerifyError(tx, 'nonstandard', 'too-long-mempool-chain', @@ -898,13 +893,13 @@ Mempool.prototype.verify = co(function* verify(entry) { // Standard verification try { - yield this.checkInputs(tx, flags1); + yield this.verifyInputs(tx, flags1); } catch (error) { if (tx.hasWitness()) throw error; // Try without segwit and cleanstack. - result = yield this.checkResult(tx, flags2); + result = yield this.verifyResult(tx, flags2); // If it failed, the first verification // was the only result we needed. @@ -913,7 +908,7 @@ Mempool.prototype.verify = co(function* verify(entry) { // If it succeeded, segwit may be causing the // failure. Try with segwit but without cleanstack. - result = yield this.checkResult(tx, flags3); + result = yield this.verifyResult(tx, flags3); // Cleanstack was causing the failure. if (result) @@ -926,7 +921,7 @@ Mempool.prototype.verify = co(function* verify(entry) { // Paranoid checks. if (this.paranoid) { - result = yield this.checkResult(tx, mandatory); + result = yield this.verifyResult(tx, mandatory); assert(result, 'BUG: Verify failed for mandatory but not standard.'); } }); @@ -939,9 +934,9 @@ Mempool.prototype.verify = co(function* verify(entry) { * @returns {Promise} */ -Mempool.prototype.checkResult = co(function* checkResult(tx, flags) { +Mempool.prototype.verifyResult = co(function* verifyResult(tx, flags) { try { - yield this.checkInputs(tx, flags); + yield this.verifyInputs(tx, flags); } catch (err) { if (err.type === 'VerifyError') return false; @@ -958,9 +953,8 @@ Mempool.prototype.checkResult = co(function* checkResult(tx, flags) { * @returns {Promise} */ -Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) { - var result = yield tx.verifyAsync(flags); - if (result) +Mempool.prototype.verifyInputs = co(function* verifyInputs(tx, flags) { + if (yield tx.verifyAsync(flags)) return; if (!(flags & constants.flags.UNSTANDARD_VERIFY_FLAGS)) { @@ -972,9 +966,7 @@ Mempool.prototype.checkInputs = co(function* checkInputs(tx, flags) { flags &= ~constants.flags.UNSTANDARD_VERIFY_FLAGS; - result = yield tx.verifyAsync(flags); - - if (result) { + if (yield tx.verifyAsync(flags)) { throw new VerifyError(tx, 'nonstandard', 'non-mandatory-script-verify-flag', @@ -1024,12 +1016,12 @@ Mempool.prototype._countAncestors = function countAncestors(tx, count, set) { set[hash] = true; count += 1; - if (count > constants.mempool.ANCESTOR_LIMIT) + if (count > this.maxAncestors) break; count = this._countAncestors(prev, count, set); - if (count > constants.mempool.ANCESTOR_LIMIT) + if (count > this.maxAncestors) break; } @@ -1263,7 +1255,87 @@ Mempool.prototype.hasOrphan = function hasOrphan(hash) { * @param {TX} tx */ -Mempool.prototype.storeOrphan = function storeOrphan(tx) { +Mempool.prototype.storeOrphan = function storeOrphan(tx, missing) { + var hash = tx.hash('hex'); + var i, input, prev; + + if (tx.getWeight() > constants.tx.MAX_WEIGHT) { + this.logger.debug('Ignoring large orphan: %s', tx.txid()); + if (!tx.hasWitness()) + this.rejects.add(tx.hash()); + return false; + } + + for (i = 0; i < missing; i++) { + prev = missing[i]; + if (this.hasReject(prev)) { + this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid()); + this.rejects.add(tx.hash()); + return false; + } + } + + for (i = 0; i < missing.length; i++) { + prev = missing[i]; + + if (!this.waiting[prev]) + this.waiting[prev] = []; + + this.waiting[prev].push(hash); + } + + this.orphans[hash] = new Orphan(tx, missing.length); + this.totalOrphans++; + + this.logger.debug('Added orphan %s to mempool.', tx.txid()); + + this.emit('add orphan', tx); + + this.limitOrphans(); + + return true; +}; + +/** + * Spend coins for transaction. + * @param {TX} tx + * @returns {Boolean} + */ + +Mempool.prototype.injectCoins = function injectCoins(tx, view) { + var missing = []; + var i, input, prevout, coin; + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + + if (!view.has(prevout.hash)) { + missing.push(prevout.hash); + continue; + } + + coin = view.getCoin(prevout.hash, prevout.index); + + if (!coin) { + throw new VerifyError(tx, + 'duplicate', + 'bad-txns-inputs-spent', + 0); + } + + input.coin = coin; + } + + return missing; +}; + +/** + * Store an orphaned transaction. + * @param {TX} tx + */ + +Mempool.prototype.maybeStoreOrphan = function maybeStoreOrphan(tx) { var missing = {}; var i, hash, input, prev; @@ -1596,8 +1668,10 @@ Mempool.prototype.getCoinView = co(function* getCoinView(tx) { if (!entry) continue; - view.addTX(entry.tx, entry.height); + view.addTX(entry.tx, -1); } + + return view; }); /** @@ -1621,6 +1695,17 @@ Mempool.prototype.verifyLocks = function verifyLocks(tx, flags) { return this.chain.verifyLocks(this.chain.tip, tx, flags); }; +/** + * Check locktime on a transaction against the current tip. + * @param {TX} tx + * @param {LockFlags} flags + * @returns {Promise} - Returns Boolean. + */ + +Mempool.prototype.verifyFinal = function verifyFinal(tx, flags) { + return this.chain.verifyFinal(this.chain.tip, tx, flags); +}; + /** * Map a transaction to the mempool. * @private diff --git a/lib/protocol/constants.js b/lib/protocol/constants.js index a334a0b6..8ebd81bf 100644 --- a/lib/protocol/constants.js +++ b/lib/protocol/constants.js @@ -478,13 +478,7 @@ exports.mempool = { * Maximum number of orphan transactions. */ - MAX_ORPHAN_TX: 100, - - /** - * Decay of minimum fee rate. - */ - - FEE_HALFLIFE: 60 * 60 * 12 + MAX_ORPHAN_TX: 100 }; /**