chain: refactor undo coins.

This commit is contained in:
Christopher Jeffrey 2016-12-01 02:56:08 -08:00
parent 873475b835
commit 3d876858f9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 75 additions and 80 deletions

View File

@ -856,27 +856,6 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) {
return this.db.has(layout.c(hash)); return this.db.has(layout.c(hash));
}; };
/**
* Get a view of the existing coins necessary to verify a block.
* @param {Block} block
* @returns {Promise} - Returns {@link CoinView}.
*/
ChainDB.prototype.getCoinView = co(function* getCoinView(block, callback) {
var view = new CoinView();
var prevout = block.getPrevout();
var i, prev, coins;
for (i = 0; i < prevout.length; i++) {
prev = prevout[i];
coins = yield this.getCoins(prev);
if (coins)
view.add(coins);
}
return view;
});
/** /**
* Get coins necessary to be resurrected during a reorg. * Get coins necessary to be resurrected during a reorg.
* @param {Hash} hash * @param {Hash} hash
@ -890,32 +869,6 @@ ChainDB.prototype.getUndoCoins = co(function* getUndoCoins(hash) {
return UndoCoins.fromRaw(data); return UndoCoins.fromRaw(data);
}); });
/**
* Get a coin view containing unspent coins as
* well as the coins to be resurrected for a reorg.
* (Note: fills block with undo coins).
* @param {Block} block
* @returns {Promise} - Returns {@link CoinView}.
*/
ChainDB.prototype.getUndoView = co(function* getUndoView(block) {
var view = yield this.getCoinView(block);
var undo = yield this.getUndoCoins(block.hash());
var index = 0;
var i, j, tx, input;
for (i = 1; i < block.txs.length; i++) {
tx = block.txs[i];
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
input.coin = undo.apply(index++, view, input.prevout);
assert(input.coin);
}
}
return view;
});
/** /**
* Retrieve a block from the database (not filled with coins). * Retrieve a block from the database (not filled with coins).
* @param {Hash} hash * @param {Hash} hash
@ -972,11 +925,24 @@ ChainDB.prototype.getRawBlock = co(function* getRawBlock(block) {
ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) { ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) {
var block = yield this.getBlock(hash); var block = yield this.getBlock(hash);
var i, j, view, undo, tx, input;
if (!block) if (!block)
return; return;
yield this.getUndoView(block); view = new CoinView();
undo = yield this.getUndoCoins(block.hash());
for (i = block.txs.length - 1; i > 0; i--) {
tx = block.txs[i];
for (j = tx.inputs.length - 1; j >= 0; j--) {
input = tx.inputs[j];
input.coin = undo.apply(view, input.prevout);
}
}
// Undo coins should be empty.
assert(undo.items.length === 0, 'Undo coins data inconsistency.');
return block; return block;
}); });
@ -1709,6 +1675,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
if (this.chain.isGenesis(block)) if (this.chain.isGenesis(block))
return; return;
// Update chain state value.
for (i = 0; i < block.txs.length; i++) { for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i]; tx = block.txs[i];
@ -1740,6 +1707,7 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
if (!view.undo.isEmpty()) if (!view.undo.isEmpty())
this.put(layout.u(block.hash()), view.undo.toRaw()); this.put(layout.u(block.hash()), view.undo.toRaw());
// Prune height-288 if pruning is enabled.
yield this.pruneBlock(block); yield this.pruneBlock(block);
}); });
@ -1750,27 +1718,21 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
*/ */
ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) { ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
var i, j, view, tx, input, output; var i, j, view, undo, tx, input, output;
if (this.options.spv) if (this.options.spv)
return; return;
view = yield this.getUndoView(block); view = new CoinView();
undo = yield this.getUndoCoins(block.hash());
this.pending.disconnect(block); this.pending.disconnect(block);
// Disconnect all transactions.
for (i = block.txs.length - 1; i >= 0; i--) { for (i = block.txs.length - 1; i >= 0; i--) {
tx = block.txs[i]; tx = block.txs[i];
if (i > 0) { for (j = tx.outputs.length - 1; j >= 0; j--) {
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
assert(input.coin);
this.pending.add(input.coin);
}
}
for (j = 0; j < tx.outputs.length; j++) {
output = tx.outputs[j]; output = tx.outputs[j];
if (output.script.isUnspendable()) if (output.script.isUnspendable())
@ -1782,10 +1744,23 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
// Remove any created coins. // Remove any created coins.
view.removeTX(tx); view.removeTX(tx);
if (i > 0) {
yield view.ensureInputs(this, tx);
for (j = tx.inputs.length - 1; j >= 0; j--) {
input = tx.inputs[j];
input.coin = undo.apply(view, input.prevout);
this.pending.add(input.coin);
}
}
// Remove from transaction index. // Remove from transaction index.
this.unindexTX(tx); this.unindexTX(tx);
} }
// Undo coins should be empty.
assert(undo.items.length === 0, 'Undo coins data inconsistency.');
// Commit new coin state. // Commit new coin state.
this.saveView(view); this.saveView(view);

View File

@ -19,11 +19,11 @@ var UndoCoins = require('./undocoins');
* @property {Object} coins * @property {Object} coins
*/ */
function CoinView(coins) { function CoinView() {
if (!(this instanceof CoinView)) if (!(this instanceof CoinView))
return new CoinView(coins); return new CoinView();
this.coins = coins || {}; this.unspent = {};
this.undo = new UndoCoins(); this.undo = new UndoCoins();
} }
@ -32,8 +32,8 @@ function CoinView(coins) {
* @param {Coins} coins * @param {Coins} coins
*/ */
CoinView.prototype.add = function add(coins) { CoinView.prototype.addCoins = function addCoins(coins) {
this.coins[coins.hash] = coins; this.unspent[coins.hash] = coins;
return coins; return coins;
}; };
@ -43,7 +43,8 @@ CoinView.prototype.add = function add(coins) {
*/ */
CoinView.prototype.addTX = function addTX(tx) { CoinView.prototype.addTX = function addTX(tx) {
return this.add(Coins.fromTX(tx)); var coins = Coins.fromTX(tx);
return this.addCoins(coins);
}; };
/** /**
@ -52,9 +53,9 @@ CoinView.prototype.addTX = function addTX(tx) {
*/ */
CoinView.prototype.removeTX = function removeTX(tx) { CoinView.prototype.removeTX = function removeTX(tx) {
var coins = this.addTX(tx); var coins = Coins.fromTX(tx);
coins.outputs.length = 0; coins.outputs.length = 0;
return coins; return this.addCoins(coins);
}; };
/** /**
@ -65,7 +66,7 @@ CoinView.prototype.removeTX = function removeTX(tx) {
*/ */
CoinView.prototype.get = function get(hash, index) { CoinView.prototype.get = function get(hash, index) {
var coins = this.coins[hash]; var coins = this.unspent[hash];
var entry; var entry;
if (!coins) if (!coins)
@ -87,7 +88,7 @@ CoinView.prototype.get = function get(hash, index) {
*/ */
CoinView.prototype.has = function has(hash, index) { CoinView.prototype.has = function has(hash, index) {
var coins = this.coins[hash]; var coins = this.unspent[hash];
if (!coins) if (!coins)
return false; return false;
@ -103,7 +104,7 @@ CoinView.prototype.has = function has(hash, index) {
*/ */
CoinView.prototype.spend = function spend(hash, index) { CoinView.prototype.spend = function spend(hash, index) {
var coins = this.coins[hash]; var coins = this.unspent[hash];
var entry, undo; var entry, undo;
if (!coins) if (!coins)
@ -133,7 +134,7 @@ CoinView.prototype.spend = function spend(hash, index) {
*/ */
CoinView.prototype.getCoins = co(function* getCoins(db, hash) { CoinView.prototype.getCoins = co(function* getCoins(db, hash) {
var coins = this.coins[hash]; var coins = this.unspent[hash];
if (!coins) { if (!coins) {
coins = yield db.getCoins(hash); coins = yield db.getCoins(hash);
@ -141,7 +142,7 @@ CoinView.prototype.getCoins = co(function* getCoins(db, hash) {
if (!coins) if (!coins)
return; return;
this.coins[hash] = coins; this.unspent[hash] = coins;
} }
return coins; return coins;
@ -172,6 +173,21 @@ CoinView.prototype.hasInputs = co(function* hasInputs(db, tx) {
return true; return true;
}); });
/**
* Read all input coins into unspent map.
* @param {ChainDB} db
* @param {TX} tx
*/
CoinView.prototype.ensureInputs = co(function* ensureInputs(db, tx) {
var i, input;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
yield this.getCoins(db, input.prevout.hash);
}
});
/** /**
* Spend coins for transaction. * Spend coins for transaction.
* @param {TX} tx * @param {TX} tx
@ -195,13 +211,13 @@ CoinView.prototype.spendCoins = function spendCoins(tx) {
*/ */
CoinView.prototype.toArray = function toArray() { CoinView.prototype.toArray = function toArray() {
var keys = Object.keys(this.coins); var keys = Object.keys(this.unspent);
var out = []; var out = [];
var i, hash; var i, hash;
for (i = 0; i < keys.length; i++) { for (i = 0; i < keys.length; i++) {
hash = keys[i]; hash = keys[i];
out.push(this.coins[hash]); out.push(this.unspent[hash]);
} }
return out; return out;

View File

@ -26,6 +26,9 @@ var decompress = compressor.decompress;
*/ */
function UndoCoins() { function UndoCoins() {
if (!(this instanceof UndoCoins))
return new UndoCoins();
this.items = []; this.items = [];
} }
@ -107,14 +110,13 @@ UndoCoins.prototype.top = function top() {
/** /**
* Re-apply undo coins to a view, effectively unspending them. * Re-apply undo coins to a view, effectively unspending them.
* @param {Number} i
* @param {CoinView} view * @param {CoinView} view
* @param {Outpoint} outpoint * @param {Outpoint} outpoint
* @returns {Coin} * @returns {Coin}
*/ */
UndoCoins.prototype.apply = function apply(i, view, outpoint) { UndoCoins.prototype.apply = function apply(view, outpoint) {
var undo = this.items[i]; var undo = this.items.pop();
var hash = outpoint.hash; var hash = outpoint.hash;
var index = outpoint.index; var index = outpoint.index;
var coins; var coins;
@ -124,19 +126,21 @@ UndoCoins.prototype.apply = function apply(i, view, outpoint) {
if (undo.height !== -1) { if (undo.height !== -1) {
coins = new Coins(); coins = new Coins();
assert(!view.coins[hash]); assert(!view.unspent[hash]);
view.coins[hash] = coins; view.unspent[hash] = coins;
coins.coinbase = undo.coinbase; coins.coinbase = undo.coinbase;
coins.height = undo.height; coins.height = undo.height;
coins.version = undo.version; coins.version = undo.version;
} else { } else {
coins = view.coins[hash]; coins = view.unspent[hash];
assert(coins); assert(coins);
} }
coins.add(index, undo.toOutput()); coins.add(index, undo.toOutput());
assert(coins.has(index));
return coins.getCoin(index); return coins.getCoin(index);
}; };