chain/mempool: optimize coinview handling. fix mempool orphan edge case.
This commit is contained in:
parent
17e473fda5
commit
459a9f25f4
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user