txdb: improve insertion handling.

This commit is contained in:
Christopher Jeffrey 2016-10-06 03:44:15 -07:00
parent f629c6db5c
commit 7506d89947
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 84 additions and 29 deletions

View File

@ -418,15 +418,17 @@ TXDB.prototype.getOrphans = co(function* getOrphans(hash, index) {
*/
TXDB.prototype.verify = co(function* verify(tx, info) {
var spends = [];
var orphans = [];
var removed = {};
var i, input, prevout, address, coin, spent, conflict;
if (tx.isCoinbase())
return true;
return orphans;
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
address = input.getHash('hex');
// Only bother if this input is ours.
@ -442,21 +444,21 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
// Skip invalid transactions
if (this.options.verify) {
if (!(yield tx.verifyInputAsync(i)))
return false;
return;
}
continue;
}
input.coin = null;
spent = yield this.isSpent(prevout.hash, prevout.index);
// Are we double-spending?
// Replace older txs with newer ones.
if (!spent)
// Orphan until we see a parent transaction.
if (!spent) {
orphans[i] = true;
continue;
}
// We must be double-spending.
coin = yield this.getSpentCoin(spent, prevout);
if (!coin)
@ -467,19 +469,42 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
// Skip invalid transactions
if (this.options.verify) {
if (!(yield tx.verifyInputAsync(i)))
return false;
return;
}
spends[i] = spent;
}
// Once we've verified everything to the
// best of our ability, go through and
// attempt to remove double-spenders.
for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i];
prevout = input.prevout;
spent = spends[i];
if (!spent)
continue;
this.logger.warning('Handling conflicting tx: %s.',
utils.revHex(spent.hash));
// Remove the older double spender.
conflict = yield this.removeConflict(spent.hash, tx);
// We have to maintain a spent list
// and pass it in. It needs to know
// which txs are considered "deleted"
// within this context.
conflict = yield this.removeConflict(spent.hash, tx, removed);
// Spender was not removed, the current
// transaction is not elligible to be added.
if (!conflict)
return false;
// Be sure to clear the batch, lest we
// remove other transactions on behalf of
// a non-eligible tx.
if (!conflict) {
this.wallet.clear();
return;
}
this.logger.warning('Removed conflict: %s.', conflict.tx.rhash);
@ -487,7 +512,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
this.emit('conflict', conflict.tx, conflict.info);
}
return true;
return orphans;
});
/**
@ -500,7 +525,7 @@ TXDB.prototype.verify = co(function* verify(tx, info) {
TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) {
var hash = tx.hash('hex');
var i, orphans, coin, input, orphan, key;
var i, orphans, coin, input, spender, orphan;
orphans = yield this.getOrphans(hash, index);
@ -511,33 +536,39 @@ TXDB.prototype.resolveOrphans = co(function* resolveOrphans(tx, index) {
coin = Coin.fromTX(tx, index);
// Add input to orphan
// Add input to resolved orphan.
for (i = 0; i < orphans.length; i++) {
orphan = orphans[i];
input = orphan.input;
spender = orphan.input;
tx = orphan.tx;
// Probably removed by some other means.
if (!tx)
continue;
tx.inputs[input.index].coin = coin;
input = tx.inputs[spender.index];
input.coin = coin;
assert(tx.inputs[input.index].prevout.hash === hash);
assert(tx.inputs[input.index].prevout.index === index);
assert(input.prevout.hash === hash);
assert(input.prevout.index === index);
// Verify that input script is correct, if not - add
// output to unspent and remove orphan from storage
if (!this.options.verify || (yield tx.verifyInputAsync(input.index))) {
key = layout.d(input.hash, input.index);
this.put(key, coin.toRaw());
if (!this.options.verify || (yield tx.verifyInputAsync(spender.index))) {
// Add the undo coin record which we never had.
this.put(layout.d(spender.hash, spender.index), coin.toRaw());
// Add the spender record back in case any evil
// transactions were removed with lazyRemove.
this.put(layout.s(hash, index), spender.toRaw());
return true;
}
yield this.lazyRemove(tx);
}
// Just going to be added again outside.
// We had orphans, but they were invalid. The
// balance will be (incorrectly) added outside.
// Subtract to compensate.
this.balance.sub(coin);
return false;
@ -592,9 +623,9 @@ TXDB.prototype._add = co(function* add(tx, info) {
// Verify and get coins.
// This potentially removes double-spenders.
result = yield this.verify(tx, info);
orphans = yield this.verify(tx, info);
if (!result)
if (!orphans)
return false;
hash = tx.hash('hex');
@ -640,8 +671,9 @@ TXDB.prototype._add = co(function* add(tx, info) {
spender = Outpoint.fromTX(tx, i).toRaw();
this.put(layout.s(prevout.hash, prevout.index), spender);
// Add orphan, if no parent transaction is yet known
if (!input.coin) {
// Add orphan if no parent transaction known.
// Do not disconnect any coins.
if (orphans[i]) {
yield this.addOrphan(prevout, spender);
continue;
}
@ -669,6 +701,9 @@ TXDB.prototype._add = co(function* add(tx, info) {
orphans = yield this.resolveOrphans(tx, i);
// If this transaction resolves an orphan,
// it should not connect coins as they are
// already spent by the orphan it resolved.
if (orphans)
continue;
@ -707,7 +742,7 @@ TXDB.prototype._add = co(function* add(tx, info) {
* @returns {Promise} - Returns Boolean.
*/
TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) {
TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref, removed) {
var tx = yield this.getTX(hash);
var info;
@ -736,7 +771,7 @@ TXDB.prototype.removeConflict = co(function* removeConflict(hash, ref) {
return;
}
info = yield this.removeRecursive(tx);
info = yield this.removeRecursive(tx, removed);
return new Conflict(tx, info);
});

View File

@ -1017,6 +1017,15 @@ Wallet.prototype.drop = function drop() {
return this.db.drop(this);
};
/**
* Clear batch.
* @private
*/
Wallet.prototype.clear = function clear() {
return this.db.clear(this);
};
/**
* Save batch.
* @returns {Promise}

View File

@ -308,6 +308,17 @@ WalletDB.prototype.drop = function drop(wallet) {
batch.clear();
};
/**
* Clear batch.
* @private
* @param {WalletID} wid
*/
WalletDB.prototype.clear = function clear(wallet) {
var batch = this.batch(wallet);
batch.clear();
};
/**
* Get batch.
* @private