chain: refactor undo coins.
This commit is contained in:
parent
873475b835
commit
3d876858f9
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user