chain: refactor tx indexing.

This commit is contained in:
Christopher Jeffrey 2016-11-30 18:17:10 -08:00
parent 7157d06464
commit aad1691f7e
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
2 changed files with 167 additions and 118 deletions

View File

@ -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',

View File

@ -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