From aad1691f7e795377431752107bff0bc0d73acdb3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 Nov 2016 18:17:10 -0800 Subject: [PATCH] chain: refactor tx indexing. --- lib/blockchain/chain.js | 4 +- lib/blockchain/chaindb.js | 281 ++++++++++++++++++++++---------------- 2 files changed, 167 insertions(+), 118 deletions(-) diff --git a/lib/blockchain/chain.js b/lib/blockchain/chain.js index d8d3dcea..2f9036e1 100644 --- a/lib/blockchain/chain.js +++ b/lib/blockchain/chain.js @@ -639,7 +639,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { tx = block.txs[i]; // Ensure tx is not double spending an output. - if (!tx.isCoinbase()) { + if (i > 0) { if (!view.fillCoins(tx)) { assert(!historical, 'BUG: Spent inputs in historical data!'); throw new VerifyError(block, @@ -677,7 +677,7 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) { } // Contextual sanity checks. - if (!tx.isCoinbase()) { + if (i > 0) { if (!tx.checkInputs(height, ret)) { throw new VerifyError(block, 'invalid', diff --git a/lib/blockchain/chaindb.js b/lib/blockchain/chaindb.js index 63487acb..cdfd56ca 100644 --- a/lib/blockchain/chaindb.js +++ b/lib/blockchain/chaindb.js @@ -1665,80 +1665,14 @@ ChainDB.prototype.removeBlock = co(function* removeBlock(hash) { }); /** - * Connect block inputs. - * @param {Block} block - * @returns {Promise} - Returns {@link Block}. + * Commit coin view to database. + * @private + * @param {CoinView} view */ -ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { - var i, j, tx, input, output, prev; - var hashes, address, hash, coins, raw; +ChainDB.prototype.saveView = function saveView(view) { + var i, coins, raw; - if (this.options.spv) - return; - - // Genesis block's coinbase is unspendable. - if (this.chain.isGenesis(block)) { - this.pending.connect(block); - return; - } - - this.pending.connect(block); - - for (i = 0; i < block.txs.length; i++) { - tx = block.txs[i]; - hash = tx.hash(); - - if (this.options.indexTX) { - this.put(layout.t(hash), tx.toExtended()); - if (this.options.indexAddress) { - hashes = tx.getHashes(); - for (j = 0; j < hashes.length; j++) { - address = hashes[j]; - this.put(layout.T(address, hash), DUMMY); - } - } - } - - if (!tx.isCoinbase()) { - for (j = 0; j < tx.inputs.length; j++) { - input = tx.inputs[j]; - - assert(input.coin); - - if (this.options.indexAddress) { - address = input.getHash(); - if (address) { - prev = input.prevout; - this.del(layout.C(address, prev.hash, prev.index)); - } - } - - this.pending.spend(input.coin); - } - } - - for (j = 0; j < tx.outputs.length; j++) { - output = tx.outputs[j]; - - if (output.script.isUnspendable()) - continue; - - if (this.options.indexAddress) { - address = output.getHash(); - if (address) - this.put(layout.C(address, hash, j), DUMMY); - } - - this.pending.add(output); - } - } - - // Write undo coins (if there are any). - if (!view.undo.isEmpty()) - this.put(layout.u(block.hash()), view.undo.toRaw()); - - // Commit new coin state. view = view.toArray(); for (i = 0; i < view.length; i++) { @@ -1752,6 +1686,56 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { this.coinCache.push(coins.hash, raw); } } +}; + +/** + * Connect block inputs. + * @param {Block} block + * @returns {Promise} - Returns {@link Block}. + */ + +ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { + var i, j, tx, input, output; + + if (this.options.spv) + return; + + this.pending.connect(block); + + // Genesis block's coinbase is unspendable. + if (this.chain.isGenesis(block)) + return; + + for (i = 0; i < block.txs.length; i++) { + tx = block.txs[i]; + + if (i > 0) { + for (j = 0; j < tx.inputs.length; j++) { + input = tx.inputs[j]; + assert(input.coin); + this.pending.spend(input.coin); + } + } + + for (j = 0; j < tx.outputs.length; j++) { + output = tx.outputs[j]; + + if (output.script.isUnspendable()) + continue; + + this.pending.add(output); + } + + // Index the transaction if enabled. + this.indexTX(tx); + } + + // Commit new coin state. + this.saveView(view); + + // Write undo coins (if there are any). + if (!view.undo.isEmpty()) + this.put(layout.u(block.hash()), view.undo.toRaw()); yield this.pruneBlock(block); }); @@ -1763,8 +1747,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) { */ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { - var i, j, tx, input, output, prev, view; - var hashes, address, hash, coins, raw; + var i, j, view, tx, hash, input, output; if (this.options.spv) return; @@ -1777,31 +1760,10 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { tx = block.txs[i]; hash = tx.hash('hex'); - if (this.options.indexTX) { - this.del(layout.t(hash)); - if (this.options.indexAddress) { - hashes = tx.getHashes(); - for (j = 0; j < hashes.length; j++) { - address = hashes[j]; - this.del(layout.T(address, hash)); - } - } - } - - if (!tx.isCoinbase()) { + if (i > 0) { for (j = 0; j < tx.inputs.length; j++) { input = tx.inputs[j]; - assert(input.coin); - - if (this.options.indexAddress) { - address = input.getHash(); - if (address) { - prev = input.prevout; - this.put(layout.C(address, prev.hash, prev.index), DUMMY); - } - } - this.pending.add(input.coin); } } @@ -1817,36 +1779,21 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { if (output.script.isUnspendable()) continue; - if (this.options.indexAddress) { - address = output.getHash(); - if (address) - this.del(layout.C(address, hash, j)); - } - // Spend added coin. view.spend(hash, j); this.pending.spend(output); } + + // Remove from transaction index. + this.unindexTX(tx); } + // Commit new coin state. + this.saveView(view); + // Remove undo coins. this.del(layout.u(block.hash())); - - // Commit new coin state. - view = view.toArray(); - - for (i = 0; i < view.length; i++) { - coins = view[i]; - if (coins.isEmpty()) { - this.del(layout.c(coins.hash)); - this.coinCache.unpush(coins.hash); - } else { - raw = coins.toRaw(); - this.put(layout.c(coins.hash), raw); - this.coinCache.push(coins.hash, raw); - } - } }); /** @@ -1889,6 +1836,108 @@ ChainDB.prototype.saveOptions = function saveOptions() { return this.db.put(layout.O, this.options.toRaw()); }; +/** + * Index a transaction by txid and address. + * @private + * @param {TX} tx + */ + +ChainDB.prototype.indexTX = function indexTX(tx) { + var hash = tx.hash(); + var i, input, output, prevout; + var hashes, addr; + + if (this.options.indexTX) { + this.put(layout.t(hash), tx.toExtended()); + if (this.options.indexAddress) { + hashes = tx.getHashes(); + for (i = 0; i < hashes.length; i++) { + addr = hashes[i]; + this.put(layout.T(addr, hash), DUMMY); + } + } + } + + if (!this.options.indexAddress) + return; + + if (!tx.isCoinbase()) { + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + addr = input.getHash(); + + assert(input.coin); + + if (!addr) + continue; + + this.del(layout.C(addr, prevout.hash, prevout.index)); + } + } + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + addr = output.getHash(); + + if (!addr) + continue; + + this.put(layout.C(addr, hash, i), DUMMY); + } +}; + +/** + * Remove transaction from index. + * @private + * @param {TX} tx + */ + +ChainDB.prototype.unindexTX = function unindexTX(tx) { + var hash = tx.hash(); + var i, input, output, prevout; + var hashes, addr; + + if (this.options.indexTX) { + this.del(layout.t(hash)); + if (this.options.indexAddress) { + hashes = tx.getHashes(); + for (i = 0; i < hashes.length; i++) { + addr = hashes[i]; + this.del(layout.T(addr, hash)); + } + } + } + + if (!this.options.indexAddress) + return; + + if (!tx.isCoinbase()) { + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + addr = input.getHash(); + + assert(input.coin); + + if (!addr) + continue; + + this.put(layout.C(addr, prevout.hash, prevout.index), DUMMY); + } + } + + for (i = 0; i < tx.outputs.length; i++) { + output = tx.outputs[i]; + addr = output.getHash(); + + if (!addr) + continue; + + this.del(layout.C(addr, hash, i)); + } +}; + /** * Chain Options * @constructor