chain/mempool: optimize coinview handling. fix mempool orphan edge case.

This commit is contained in:
Christopher Jeffrey 2017-09-26 13:07:01 -07:00
parent 17e473fda5
commit 459a9f25f4
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 150 additions and 158 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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