diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index 82f82a8d..bb3e9450 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -2164,12 +2164,11 @@ Chain.prototype._getLocator = async function _getLocator(start) { hashes.push(start); } + let main = await this.isMainChain(entry); let hash = entry.hash; let height = entry.height; let step = 1; - const main = await this.isMainChain(entry); - hashes.push(hash); while (height > 0) { @@ -2189,6 +2188,7 @@ Chain.prototype._getLocator = async function _getLocator(start) { } else { const ancestor = await this.getAncestor(entry, height); assert(ancestor); + main = await this.isMainChain(ancestor); hash = ancestor.hash; } diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index 9761d3cf..0b1437f3 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -15,7 +15,6 @@ const Amount = require('../btc/amount'); const encoding = require('../utils/encoding'); const Network = require('../protocol/network'); const CoinView = require('../coins/coinview'); -const Coins = require('../coins/coins'); const UndoCoins = require('../coins/undocoins'); const LDB = require('../db/ldb'); const layout = require('./layout'); @@ -558,6 +557,7 @@ ChainDB.prototype.getFlags = async function getFlags() { ChainDB.prototype.verifyFlags = async function verifyFlags(state) { const options = this.options; const flags = await this.getFlags(); + let needsSave = false; let needsPrune = false; @@ -993,13 +993,8 @@ ChainDB.prototype.getCoinView = async function getCoinView(tx) { for (const {prevout} of tx.inputs) { const coin = await this.readCoin(prevout); - if (!coin) { - const coins = new Coins(); - view.add(prevout.hash, coins); - continue; - } - - view.addEntry(prevout, coin); + if (coin) + view.addEntry(prevout, coin); } return view; @@ -1014,16 +1009,20 @@ ChainDB.prototype.getCoinView = async function getCoinView(tx) { ChainDB.prototype.getSpentView = async function getSpentView(tx) { const view = await this.getCoinView(tx); - for (const [hash, coins] of view.map) { - if (!coins.isEmpty()) + for (const {prevout} of tx.inputs) { + if (view.hasEntry(prevout)) continue; + const {hash, index} = prevout; const meta = await this.getMeta(hash); if (!meta) continue; - view.addTX(meta.tx, meta.height); + const {tx, height} = meta; + + if (index < tx.outputs.length) + view.addIndex(tx, index, height); } return view; diff --git a/lib/coins/coins.js b/lib/coins/coins.js index 97516f68..1eb2affc 100644 --- a/lib/coins/coins.js +++ b/lib/coins/coins.js @@ -31,11 +31,9 @@ function Coins() { */ Coins.prototype.add = function add(index, coin) { - assert(index >= 0); - assert(!this.outputs.has(index)); - + assert((index >>> 0) === index); + assert(coin); this.outputs.set(index, coin); - return coin; }; @@ -47,10 +45,21 @@ Coins.prototype.add = function add(index, coin) { */ Coins.prototype.addOutput = function addOutput(index, output) { - assert(!output.script.isUnspendable()); return this.add(index, CoinEntry.fromOutput(output)); }; +/** + * Add an output to the collection by output index. + * @param {TX} tx + * @param {Number} index + * @param {Number} height + * @returns {CoinEntry} + */ + +Coins.prototype.addIndex = function addIndex(tx, index, height) { + return this.add(index, CoinEntry.fromTX(tx, index, height)); +}; + /** * Add a single coin to the collection. * @param {Coin} coin @@ -58,7 +67,6 @@ Coins.prototype.addOutput = function addOutput(index, output) { */ Coins.prototype.addCoin = function addCoin(coin) { - assert(!coin.script.isUnspendable()); return this.add(coin.index, CoinEntry.fromCoin(coin)); }; @@ -187,7 +195,9 @@ Coins.prototype.fromTX = function fromTX(tx, height) { if (output.script.isUnspendable()) continue; - this.outputs.set(i, CoinEntry.fromTX(tx, i, height)); + const entry = CoinEntry.fromTX(tx, i, height); + + this.outputs.set(i, entry); } return this; diff --git a/lib/coins/coinview.js b/lib/coins/coinview.js index 83689625..38898585 100644 --- a/lib/coins/coinview.js +++ b/lib/coins/coinview.js @@ -59,6 +59,21 @@ CoinView.prototype.add = function add(hash, coins) { return coins; }; +/** + * Ensure existence of coins object in the collection. + * @param {Hash} hash + * @returns {Coins} + */ + +CoinView.prototype.ensure = function ensure(hash) { + const coins = this.map.get(hash); + + if (coins) + return coins; + + return this.add(hash, new Coins()); +}; + /** * Remove coins from the collection. * @param {Coins} coins @@ -115,19 +130,7 @@ CoinView.prototype.removeTX = function removeTX(tx, height) { CoinView.prototype.addEntry = function addEntry(prevout, coin) { const {hash, index} = prevout; - let coins = this.get(hash); - - if (!coins) { - coins = new Coins(); - this.add(hash, coins); - } - - if (coin.output.script.isUnspendable()) - return null; - - if (coins.has(index)) - return null; - + const coins = this.ensure(hash); return coins.add(index, coin); }; @@ -138,20 +141,7 @@ CoinView.prototype.addEntry = function addEntry(prevout, coin) { */ CoinView.prototype.addCoin = function addCoin(coin) { - const {hash, index} = coin; - let coins = this.get(hash); - - if (!coins) { - coins = new Coins(); - this.add(hash, coins); - } - - if (coin.script.isUnspendable()) - return null; - - if (coins.has(index)) - return null; - + const coins = this.ensure(coin.hash); return coins.addCoin(coin); }; @@ -164,22 +154,24 @@ CoinView.prototype.addCoin = function addCoin(coin) { CoinView.prototype.addOutput = function addOutput(prevout, output) { const {hash, index} = prevout; - let coins = this.get(hash); - - if (!coins) { - coins = new Coins(); - this.add(hash, coins); - } - - if (output.script.isUnspendable()) - return null; - - if (coins.has(index)) - return null; - + const coins = this.ensure(hash); return coins.addOutput(index, output); }; +/** + * Add an output to the collection by output index. + * @param {TX} tx + * @param {Number} index + * @param {Number} height + * @returns {CoinEntry|null} + */ + +CoinView.prototype.addIndex = function addIndex(tx, index, height) { + const hash = tx.hash('hex'); + const coins = this.ensure(hash); + return coins.addIndex(tx, index, height); +}; + /** * Spend an output. * @param {Outpoint} prevout @@ -448,11 +440,16 @@ CoinView.prototype.readInputs = async function readInputs(db, tx) { */ CoinView.prototype.spendInputs = async function spendInputs(db, tx) { - if (tx.inputs.length < 4) { + let i = 0; + + while (i < tx.inputs.length) { + const len = Math.min(i + 4, tx.inputs.length); const jobs = []; - for (const {prevout} of tx.inputs) + for (; i < len; i++) { + const {prevout} = tx.inputs[i]; jobs.push(this.readCoin(db, prevout)); + } const coins = await Promise.all(jobs); @@ -463,18 +460,6 @@ CoinView.prototype.spendInputs = async function spendInputs(db, tx) { coin.spent = true; this.undo.push(coin); } - - return true; - } - - for (const {prevout} of tx.inputs) { - const coin = await this.readCoin(db, prevout); - - if (!coin || coin.spent) - return false; - - coin.spent = true; - this.undo.push(coin); } return true; diff --git a/lib/mempool/mempool.js b/lib/mempool/mempool.js index d7621ace..c61cad83 100644 --- a/lib/mempool/mempool.js +++ b/lib/mempool/mempool.js @@ -28,7 +28,6 @@ const layout = require('./layout'); const LDB = require('../db/ldb'); const Fees = require('./fees'); const CoinView = require('../coins/coinview'); -const Coins = require('../coins/coins'); const Heap = require('../utils/heap'); /** @@ -822,12 +821,12 @@ Mempool.prototype.insertTX = async function insertTX(tx, id) { // pertains to the mempool. const view = await this.getCoinView(tx); - // Find missing outpoints. - const missing = this.findMissing(tx, view); - // Maybe store as an orphan. + const missing = this.maybeOrphan(tx, view, id); + + // Return missing outpoint hashes. if (missing) - return this.storeOrphan(tx, missing, id); + return missing; // Create a new mempool entry // at current chain height. @@ -1164,8 +1163,8 @@ Mempool.prototype.updateAncestors = function updateAncestors(entry, map) { Mempool.prototype._countAncestors = function _countAncestors(entry, set, child, map) { const tx = entry.tx; - for (const input of tx.inputs) { - const hash = input.prevout.hash; + for (const {prevout} of tx.inputs) { + const hash = prevout.hash; const parent = this.getEntry(hash); if (!parent) @@ -1255,8 +1254,8 @@ Mempool.prototype.getAncestors = function getAncestors(entry) { Mempool.prototype._getAncestors = function _getAncestors(entry, entries, set) { const tx = entry.tx; - for (const input of tx.inputs) { - const hash = input.prevout.hash; + for (const {prevout} of tx.inputs) { + const hash = prevout.hash; const parent = this.getEntry(hash); if (!parent) @@ -1342,9 +1341,8 @@ Mempool.prototype.getDepends = function getDepends(tx) { */ Mempool.prototype.hasDepends = function hasDepends(tx) { - for (const input of tx.inputs) { - const hash = input.prevout.hash; - if (this.hasEntry(hash)) + for (const {prevout} of tx.inputs) { + if (this.hasEntry(prevout.hash)) return true; } return false; @@ -1405,40 +1403,63 @@ Mempool.prototype.hasOrphan = function hasOrphan(hash) { }; /** - * Store an orphaned transaction. + * Maybe store an orphaned transaction. * @param {TX} tx - * @param {Hash[]} missing + * @param {CoinView} view * @param {Number} id */ -Mempool.prototype.storeOrphan = function storeOrphan(tx, missing, id) { +Mempool.prototype.maybeOrphan = function maybeOrphan(tx, view, id) { + const hashes = new Set(); + const missing = []; + + for (const {prevout} of tx.inputs) { + if (view.hasEntry(prevout)) + continue; + + if (this.hasReject(prevout.hash)) { + this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid()); + this.rejects.add(tx.hash()); + return missing; + } + + if (this.hasEntry(prevout.hash)) { + this.logger.debug( + 'Not storing orphan %s (non-existent output).', + tx.txid()); + this.rejects.add(tx.hash()); + return missing; + } + + hashes.add(prevout.hash); + } + + // Not an orphan. + if (hashes.size === 0) + return null; + + // Weight limit for orphans. if (tx.getWeight() > policy.MAX_TX_WEIGHT) { this.logger.debug('Ignoring large orphan: %s', tx.txid()); if (!tx.hasWitness()) this.rejects.add(tx.hash()); - return []; - } - - for (const prev of missing) { - if (this.hasReject(prev)) { - this.logger.debug('Not storing orphan %s (rejected parents).', tx.txid()); - this.rejects.add(tx.hash()); - return []; - } + return missing; } if (this.options.maxOrphans === 0) - return []; + return missing; this.limitOrphans(); const hash = tx.hash('hex'); - for (const prev of missing) { + for (const prev of hashes.keys()) { if (!this.waiting.has(prev)) this.waiting.set(prev, new Set()); this.waiting.get(prev).add(hash); + + missing.push(prev); } this.orphans.set(hash, new Orphan(tx, missing.length, id)); @@ -1473,7 +1494,7 @@ Mempool.prototype.handleOrphans = async function handleOrphans(parent) { } try { - missing = await this.insertTX(tx, -1); + missing = await this.insertTX(tx, orphan.id); } catch (err) { if (err.type === 'VerifyError') { this.logger.debug( @@ -1490,7 +1511,7 @@ Mempool.prototype.handleOrphans = async function handleOrphans(parent) { throw err; } - assert(!missing); + assert(!missing || missing.length === 0); this.logger.debug('Resolved orphan %s in mempool.', tx.txid()); } @@ -1516,13 +1537,13 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(parent) { const resolved = []; - for (const orphanHash of set.keys()) { - const orphan = this.getOrphan(orphanHash); + for (const hash of set.keys()) { + const orphan = this.getOrphan(hash); assert(orphan); if (--orphan.missing === 0) { - this.orphans.delete(orphanHash); + this.orphans.delete(hash); resolved.push(orphan); } } @@ -1614,9 +1635,9 @@ Mempool.prototype.limitOrphans = function limitOrphans() { */ Mempool.prototype.isDoubleSpend = function isDoubleSpend(tx) { - for (const input of tx.inputs) { - const prevout = input.prevout; - if (this.isSpent(prevout.hash, prevout.index)) + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + if (this.isSpent(hash, index)) return true; } @@ -1650,50 +1671,24 @@ Mempool.prototype.getCoinView = async function getCoinView(tx) { const view = new CoinView(); for (const {prevout} of tx.inputs) { - const entry = this.getEntry(prevout.hash); + const {hash, index} = prevout; + const tx = this.getTX(hash); - if (entry) { - view.addTX(entry.tx, -1); + if (tx) { + if (index < tx.outputs.length) + view.addIndex(tx, index, -1); continue; } const coin = await this.chain.readCoin(prevout); - if (!coin) { - const coins = new Coins(); - view.add(prevout.hash, coins); - continue; - } - - view.addEntry(prevout, coin); + if (coin) + view.addEntry(prevout, coin); } return view; }; -/** - * Find missing outpoints. - * @param {TX} tx - * @param {CoinView} view - * @returns {Hash[]} - */ - -Mempool.prototype.findMissing = function findMissing(tx, view) { - const missing = []; - - for (const {prevout} of tx.inputs) { - if (view.hasEntry(prevout)) - continue; - - missing.push(prevout.hash); - } - - if (missing.length === 0) - return null; - - return missing; -}; - /** * Get a snapshot of all transaction hashes in the mempool. Used * for generating INV packets in response to MEMPOOL packets. @@ -1748,8 +1743,8 @@ Mempool.prototype.trackEntry = function trackEntry(entry, view) { assert(!tx.isCoinbase()); - for (const input of tx.inputs) { - const key = input.prevout.toKey(); + for (const {prevout} of tx.inputs) { + const key = prevout.toKey(); this.spents.set(key, entry); } @@ -1774,8 +1769,8 @@ Mempool.prototype.untrackEntry = function untrackEntry(entry) { assert(!tx.isCoinbase()); - for (const input of tx.inputs) { - const key = input.prevout.toKey(); + for (const {prevout} of tx.inputs) { + const key = prevout.toKey(); this.spents.delete(key); } @@ -1797,9 +1792,9 @@ Mempool.prototype.indexEntry = function indexEntry(entry, view) { this.txIndex.insert(entry, view); - for (const input of tx.inputs) { - const prev = input.prevout; - this.coinIndex.remove(prev.hash, prev.index); + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + this.coinIndex.remove(hash, index); } for (let i = 0; i < tx.outputs.length; i++) @@ -1818,14 +1813,14 @@ Mempool.prototype.unindexEntry = function unindexEntry(entry) { this.txIndex.remove(hash); - for (const input of tx.inputs) { - const prevout = input.prevout.hash; - const prev = this.getTX(prevout.hash); + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const prev = this.getTX(hash); if (!prev) continue; - this.coinIndex.insert(prev, prevout.index); + this.coinIndex.insert(prev, index); } for (let i = 0; i < tx.outputs.length; i++) @@ -1840,9 +1835,9 @@ Mempool.prototype.unindexEntry = function unindexEntry(entry) { */ Mempool.prototype.removeDoubleSpends = function removeDoubleSpends(tx) { - for (const input of tx.inputs) { - const prevout = input.prevout; - const spent = this.getSpent(prevout.hash, prevout.index); + for (const {prevout} of tx.inputs) { + const {hash, index} = prevout; + const spent = this.getSpent(hash, index); if (!spent) continue; diff --git a/lib/net/pool.js b/lib/net/pool.js index 6df873cb..7732f506 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -1691,7 +1691,8 @@ Pool.prototype.handleBlockInv = async function handleBlockInv(peer, hashes) { peer.hostname()); const items = []; - let exists; + + let exists = null; for (let i = 0; i < hashes.length; i++) { const hash = hashes[i]; @@ -3392,6 +3393,7 @@ Pool.prototype.getTX = function getTX(peer, hashes) { throw new Error('Peer is destroyed (getdata).'); let now = util.ms(); + const items = []; for (const hash of hashes) { @@ -3551,6 +3553,7 @@ Pool.prototype.resolveItem = function resolveItem(peer, item) { Pool.prototype.broadcast = function broadcast(msg) { const hash = msg.hash('hex'); + let item = this.invMap.get(hash); if (item) { diff --git a/lib/primitives/mtx.js b/lib/primitives/mtx.js index 6ae12cf4..6dfe8a46 100644 --- a/lib/primitives/mtx.js +++ b/lib/primitives/mtx.js @@ -197,10 +197,10 @@ MTX.prototype.addTX = function addTX(tx, index, height) { height = -1; const input = Input.fromTX(tx, index); - const coin = Coin.fromTX(tx, index, height); this.inputs.push(input); - this.view.addCoin(coin); + + this.view.addIndex(tx, index, height); return input; };