txdb: comments. serialization.
This commit is contained in:
parent
557c1044cc
commit
2f72937686
@ -2503,6 +2503,133 @@ TX.fromExtended = function fromExtended(data, saveCoins, enc) {
|
||||
return new TX().fromExtended(data, saveCoins);
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize a transaction to BCoin "extended format".
|
||||
* This is the serialization format BCoin uses internally
|
||||
* to store transactions in the database. The extended
|
||||
* serialization includes the height, block hash, index,
|
||||
* timestamp, pending-since time, and optionally a vector
|
||||
* for the serialized coins.
|
||||
* @param {Boolean?} saveCoins - Whether to serialize the coins.
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
TX.prototype.toExtended2 = function toExtended2(saveCoins, writer) {
|
||||
var p = BufferWriter(writer);
|
||||
var i, input, field, bit, oct;
|
||||
|
||||
this.toRaw(p);
|
||||
|
||||
p.writeU32(this.ps);
|
||||
|
||||
if (this.height !== -1) {
|
||||
assert(this.block != null);
|
||||
assert(this.height !== -1);
|
||||
assert(this.index !== -1);
|
||||
assert(this.ts !== 0);
|
||||
p.writeU8(1);
|
||||
p.writeHash(this.block);
|
||||
p.writeU32(this.height);
|
||||
p.writeU32(this.ts);
|
||||
p.writeU32(this.index);
|
||||
} else {
|
||||
p.writeU8(0);
|
||||
}
|
||||
|
||||
if (saveCoins) {
|
||||
field = new Buffer(Math.ceil(this.inputs.length / 8));
|
||||
field.fill(0);
|
||||
|
||||
p.writeBytes(field);
|
||||
|
||||
for (i = 0; i < this.inputs.length; i++) {
|
||||
input = this.inputs[i];
|
||||
|
||||
if (!input.coin) {
|
||||
bit = i % 8;
|
||||
oct = (i - bit) / 8;
|
||||
field[oct] |= 1 << (7 - bit);
|
||||
continue;
|
||||
}
|
||||
|
||||
input.coin.toRaw(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (!writer)
|
||||
p = p.render();
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inject properties from "extended" serialization format.
|
||||
* @param {Buffer} data
|
||||
* @param {Boolean?} saveCoins - If true, the function will
|
||||
* attempt to parse the coins.
|
||||
*/
|
||||
|
||||
TX.prototype.fromExtended2 = function fromExtended2(data, saveCoins) {
|
||||
var p = BufferReader(data);
|
||||
var i, input, coin, field, bit, oct, spent;
|
||||
|
||||
this.fromRaw(p);
|
||||
|
||||
this.ps = p.readU32();
|
||||
|
||||
if (p.readU8() == 1) {
|
||||
this.block = p.readHash('hex');
|
||||
this.height = p.readU32();
|
||||
this.ts = p.readU32();
|
||||
this.index = p.readU32();
|
||||
}
|
||||
|
||||
if (saveCoins) {
|
||||
field = p.readBytes(Math.ceil(this.inputs.length / 8), true);
|
||||
|
||||
for (i = 0; i < this.inputs.length; i++) {
|
||||
input = this.inputs[i];
|
||||
|
||||
bit = i % 8;
|
||||
oct = (i - bit) / 8;
|
||||
spent = (field[oct] >>> (7 - bit)) & 1;
|
||||
|
||||
if (spent)
|
||||
continue;
|
||||
|
||||
coin = Coin.fromRaw(p);
|
||||
coin.hash = input.prevout.hash;
|
||||
coin.index = input.prevout.index;
|
||||
|
||||
input.coin = coin;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instantiate a transaction from a Buffer
|
||||
* in "extended" serialization format.
|
||||
* @param {Buffer} data
|
||||
* @param {Boolean?} saveCoins - If true, the function will
|
||||
* attempt to parse the coins.
|
||||
* @param {String?} enc - One of `"hex"` or `null`.
|
||||
* @returns {TX}
|
||||
*/
|
||||
|
||||
TX.fromExtended2 = function fromExtended2(data, saveCoins, enc) {
|
||||
if (typeof saveCoins === 'string') {
|
||||
enc = saveCoins;
|
||||
saveCoins = false;
|
||||
}
|
||||
|
||||
if (typeof data === 'string')
|
||||
data = new Buffer(data, enc);
|
||||
|
||||
return new TX().fromExtended2(data, saveCoins);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether an object is a TX.
|
||||
* @param {Object} obj
|
||||
|
||||
@ -491,6 +491,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
|
||||
if (tx.isCoinbase())
|
||||
return true;
|
||||
|
||||
// We already have this one.
|
||||
if (this.count[hash])
|
||||
return false;
|
||||
|
||||
@ -498,6 +499,7 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
|
||||
input = tx.inputs[i];
|
||||
prevout = input.prevout;
|
||||
|
||||
// If we have a coin, we can verify this right now.
|
||||
coin = yield this.getCoin(prevout.hash, prevout.index);
|
||||
|
||||
if (coin) {
|
||||
@ -510,27 +512,37 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check whether the input is already part of the stxo set.
|
||||
spent = yield this.getSpent(prevout.hash, prevout.index);
|
||||
|
||||
if (spent) {
|
||||
coin = yield this.getSpentCoin(spent, prevout);
|
||||
|
||||
if (!coin)
|
||||
// If we don't have an undo coin,
|
||||
// this is an unknown input we
|
||||
// decided to index for the hell
|
||||
// of it.
|
||||
if (coin) {
|
||||
if (this.options.verify && tx.height === -1) {
|
||||
input.coin = coin;
|
||||
if (!(yield tx.verifyInputAsync(i)))
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
|
||||
if (this.options.verify && tx.height === -1) {
|
||||
input.coin = coin;
|
||||
if (!(yield tx.verifyInputAsync(i)))
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// This is a HACK: try to extract an
|
||||
// address from the input to tell if
|
||||
// it's ours and should be regarded
|
||||
// as an orphan input.
|
||||
if (yield this.hasPath(input))
|
||||
orphans[i] = true;
|
||||
}
|
||||
|
||||
// Store orphans now that we've
|
||||
// fully verified everything to
|
||||
// the best of our ability.
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
prevout = input.prevout;
|
||||
@ -540,6 +552,11 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
|
||||
|
||||
key = prevout.hash + prevout.index;
|
||||
|
||||
// In theory, someone could try to DoS
|
||||
// us by creating tons of fake transactions
|
||||
// with our pubkey in the scriptsig. This
|
||||
// is due to the hack mentioned above. Only
|
||||
//allow 20 orphans at a time.
|
||||
if (this.totalOrphans > 20) {
|
||||
this.logger.warning('Potential orphan flood!');
|
||||
this.logger.warning(
|
||||
@ -563,6 +580,8 @@ TXDB.prototype.verifyInputs = co(function* verifyInputs(tx) {
|
||||
hasOrphans = true;
|
||||
}
|
||||
|
||||
// We wait til _all_ orphans are resolved
|
||||
// before inserting this transaction.
|
||||
if (hasOrphans)
|
||||
return false;
|
||||
|
||||
@ -584,6 +603,10 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
|
||||
if (!resolved)
|
||||
resolved = [];
|
||||
|
||||
// Always push the first transaction on.
|
||||
// Necessary for the first resolved tx
|
||||
// as well as the recursive behavior
|
||||
// below.
|
||||
resolved.push(tx);
|
||||
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
@ -598,6 +621,12 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
|
||||
|
||||
coin = Coin.fromTX(tx, i);
|
||||
|
||||
// Note that their might be multiple
|
||||
// orphans per input, either due to
|
||||
// double spends or an adversary
|
||||
// creating fake transactions. We
|
||||
// take the first valid orphan
|
||||
// transaction.
|
||||
for (j = 0; j < orphans.length; j++) {
|
||||
orphan = orphans[j];
|
||||
valid = true;
|
||||
@ -607,11 +636,14 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
|
||||
assert(input.prevout.hash === hash);
|
||||
assert(input.prevout.index === i);
|
||||
|
||||
// We can finally verify this input.
|
||||
if (this.options.verify && orphan.tx.height === -1) {
|
||||
input.coin = coin;
|
||||
valid = yield orphan.tx.verifyInputAsync(orphan.index);
|
||||
}
|
||||
|
||||
// If it's valid and fully resolved,
|
||||
// we can resolve _its_ outputs.
|
||||
if (valid) {
|
||||
if (--this.count[orphan.hash] === 0) {
|
||||
delete this.count[orphan.hash];
|
||||
@ -620,6 +652,7 @@ TXDB.prototype.resolveOutputs = co(function* resolveOutputs(tx, resolved) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Forget about it if invalid.
|
||||
delete this.count[orphan.hash];
|
||||
}
|
||||
}
|
||||
@ -722,16 +755,27 @@ TXDB.prototype.resolveInput = co(function* resolveInput(tx, i, path) {
|
||||
if (!spent)
|
||||
return false;
|
||||
|
||||
// If we have an undo coin, we
|
||||
// already knew about this input.
|
||||
if (yield this.hasSpentCoin(spent))
|
||||
return false;
|
||||
|
||||
// Get the spending transaction so
|
||||
// we can properly add the undo coin.
|
||||
stx = yield this.getTX(spent.hash);
|
||||
assert(stx);
|
||||
|
||||
// Crete the credit and add the undo coin.
|
||||
credit = Credit.fromTX(tx, i);
|
||||
|
||||
this.spendCredit(credit, stx, spent.index);
|
||||
|
||||
// If the spender is unconfirmed, save
|
||||
// the credit as well, and mark it as
|
||||
// unspent in the mempool. This is the
|
||||
// same behavior `insert` would have
|
||||
// done for inputs. We're just doing
|
||||
// it retroactively.
|
||||
if (stx.height === -1) {
|
||||
credit.spent = true;
|
||||
this.saveCredit(credit, path);
|
||||
@ -816,7 +860,8 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Add transaction, runs `confirm()` and `verify()`.
|
||||
* Add transaction, potentially runs
|
||||
* `confirm()` and `removeConflicts()`.
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
@ -839,7 +884,7 @@ TXDB.prototype.add = co(function* add(tx) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Add transaction without a lock.
|
||||
* Add transaction without a batch.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
@ -864,7 +909,7 @@ TXDB.prototype._add = co(function* add(tx) {
|
||||
// Save the original time.
|
||||
tx.ps = existing.ps;
|
||||
|
||||
// Attempt to confirm tx before adding it.
|
||||
// Confirm transaction.
|
||||
return yield this.confirm(tx);
|
||||
}
|
||||
|
||||
@ -876,6 +921,9 @@ TXDB.prototype._add = co(function* add(tx) {
|
||||
// We ignore any unconfirmed txs
|
||||
// that are replace-by-fee.
|
||||
if (yield this.isRBF(tx)) {
|
||||
// We need to index every spender
|
||||
// hash to detect "passive"
|
||||
// replace-by-fee.
|
||||
this.put(layout.r(hash), DUMMY);
|
||||
return;
|
||||
}
|
||||
@ -887,29 +935,42 @@ TXDB.prototype._add = co(function* add(tx) {
|
||||
this.del(layout.r(hash));
|
||||
}
|
||||
|
||||
return yield this._insert(tx);
|
||||
// Finally we can do a regular insertion.
|
||||
return yield this.insert(tx);
|
||||
});
|
||||
|
||||
/**
|
||||
* Add transaction without a lock.
|
||||
* Insert transaction.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype._insert = co(function* insert(tx) {
|
||||
TXDB.prototype.insert = co(function* insert(tx) {
|
||||
var hash = tx.hash('hex');
|
||||
var details = new Details(this, tx);
|
||||
var i, input, output, coin;
|
||||
var prevout, credit, path, account;
|
||||
|
||||
if (!tx.isCoinbase()) {
|
||||
// We need to potentially spend some coins here.
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
prevout = input.prevout;
|
||||
credit = yield this.getCredit(prevout.hash, prevout.index);
|
||||
|
||||
if (!credit) {
|
||||
// Maintain an stxo list for every
|
||||
// spent input (even ones we don't
|
||||
// recognize). This is used for
|
||||
// detecting double-spends (as best
|
||||
// we can), as well as resolving
|
||||
// inputs we didn't know were ours
|
||||
// at the time. This built-in error
|
||||
// correction is not technically
|
||||
// necessary assuming no messages
|
||||
// are ever missed from the mempool,
|
||||
// but shit happens.
|
||||
this.writeInput(tx, i);
|
||||
continue;
|
||||
}
|
||||
@ -918,16 +979,37 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
path = yield this.getPath(coin);
|
||||
assert(path);
|
||||
|
||||
// Build the tx details object
|
||||
// as we go, for speed.
|
||||
details.setInput(i, path, coin);
|
||||
|
||||
// Write an undo coin for the credit
|
||||
// and add it to the stxo set.
|
||||
this.spendCredit(credit, tx, i);
|
||||
|
||||
// Unconfirmed balance should always
|
||||
// be updated as it reflects the on-chain
|
||||
// balance _and_ mempool balance assuming
|
||||
// everything in the mempool were to confirm.
|
||||
this.pending.unconfirmed -= coin.value;
|
||||
|
||||
if (tx.height === -1) {
|
||||
// If the tx is not mined, we do not
|
||||
// disconnect the coin, we simply mark
|
||||
// a `spent` flag on the credit. This
|
||||
// effectively prevents the mempool
|
||||
// from altering our utxo state
|
||||
// permanently. It also makes it
|
||||
// possible to compare the on-chain
|
||||
// state vs. the mempool state.
|
||||
credit.spent = true;
|
||||
this.saveCredit(credit, path);
|
||||
} else {
|
||||
// If the tx is mined, we can safely
|
||||
// remove the coin being spent. This
|
||||
// coin will be indexed as an undo
|
||||
// coin so it can be reconnected
|
||||
// later during a reorg.
|
||||
this.pending.coin--;
|
||||
this.pending.confirmed -= coin.value;
|
||||
this.removeCredit(credit, path);
|
||||
@ -935,6 +1017,7 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Potentially add coins to the utxo set.
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
path = yield this.getPath(output);
|
||||
@ -944,6 +1027,8 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
|
||||
details.setOutput(i, path);
|
||||
|
||||
// Attempt to resolve an input we
|
||||
// did not know was ours at the time.
|
||||
if (yield this.resolveInput(tx, i, path))
|
||||
continue;
|
||||
|
||||
@ -958,6 +1043,8 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
|
||||
// Save and index the transaction in bcoin's
|
||||
// own "extended" transaction serialization.
|
||||
this.put(layout.t(hash), tx.toExtended());
|
||||
this.put(layout.m(tx.ps, hash), DUMMY);
|
||||
|
||||
@ -966,6 +1053,9 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
else
|
||||
this.put(layout.h(tx.height, hash), DUMMY);
|
||||
|
||||
// Do some secondary indexing for account-based
|
||||
// queries. This saves us a lot of time for
|
||||
// queries later.
|
||||
for (i = 0; i < details.accounts.length; i++) {
|
||||
account = details.accounts[i];
|
||||
|
||||
@ -978,16 +1068,23 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
this.put(layout.H(account, tx.height, hash), DUMMY);
|
||||
}
|
||||
|
||||
// Update the transaction counter and
|
||||
// commit the new state. This state will
|
||||
// only overwrite the best state once
|
||||
// the batch has actually been written
|
||||
// to disk.
|
||||
this.pending.tx++;
|
||||
this.put(layout.R, this.pending.commit());
|
||||
|
||||
// This transaction may unlock some
|
||||
// coins now that we've seen it.
|
||||
this.unlockTX(tx);
|
||||
|
||||
// Emit events for potential local and
|
||||
// websocket listeners. Note that these
|
||||
// will only be emitted if the batch is
|
||||
// successfully written to disk.
|
||||
this.emit('tx', tx, details);
|
||||
|
||||
if (tx.height !== -1)
|
||||
this.emit('confirmed', tx, details);
|
||||
|
||||
this.emit('balance', this.pending.toBalance(), details);
|
||||
|
||||
return details;
|
||||
@ -997,9 +1094,7 @@ TXDB.prototype._insert = co(function* insert(tx) {
|
||||
* Attempt to confirm a transaction.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {Promise} - Returns Boolean. `false` if
|
||||
* the transaction should be added to the database, `true` if the
|
||||
* transaction was confirmed, or should be ignored.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
@ -1011,6 +1106,9 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
if (!tx.isCoinbase()) {
|
||||
credits = yield this.getSpentCredits(tx);
|
||||
|
||||
// Potentially spend coins. Now that the tx
|
||||
// is mined, we can actually _remove_ coins
|
||||
// from the utxo state.
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
prevout = input.prevout;
|
||||
@ -1024,6 +1122,11 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
if (!credit)
|
||||
continue;
|
||||
|
||||
// Add a spend record and undo coin
|
||||
// for the coin we now know is ours.
|
||||
// We don't need to remove the coin
|
||||
// since it was never added in the
|
||||
// first place.
|
||||
this.spendCredit(credit, tx, i);
|
||||
|
||||
this.pending.unconfirmed -= credit.coin.value;
|
||||
@ -1038,6 +1141,9 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
|
||||
details.setInput(i, path, coin);
|
||||
|
||||
// We can now safely remove the credit
|
||||
// entirely, now that we know it's also
|
||||
// been removed on-chain.
|
||||
this.pending.coin--;
|
||||
this.pending.confirmed -= coin.value;
|
||||
|
||||
@ -1045,6 +1151,7 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update credit heights, including undo coins.
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
path = yield this.getPath(output);
|
||||
@ -1054,15 +1161,18 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
|
||||
details.setOutput(i, path);
|
||||
|
||||
if (yield this.resolveInput(tx, i, path))
|
||||
continue;
|
||||
|
||||
credit = yield this.getCredit(hash, i);
|
||||
assert(credit);
|
||||
|
||||
// Credits spent in the mempool add an
|
||||
// undo coin for ease. If this credit is
|
||||
// spent in the mempool, we need to
|
||||
// update the undo coin's height.
|
||||
if (credit.spent)
|
||||
yield this.updateSpentCoin(tx, i);
|
||||
|
||||
// Update coin height and confirmed
|
||||
// balance. Save once again.
|
||||
coin = credit.coin;
|
||||
coin.height = tx.height;
|
||||
|
||||
@ -1072,21 +1182,25 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
|
||||
// Save the new serialized transaction as
|
||||
// the block-related properties have been
|
||||
// updated. Also reindex for height.
|
||||
this.put(layout.t(hash), tx.toExtended());
|
||||
this.del(layout.p(hash));
|
||||
this.put(layout.h(tx.height, hash), DUMMY);
|
||||
|
||||
// Secondary indexing also needs to change.
|
||||
for (i = 0; i < details.accounts.length; i++) {
|
||||
account = details.accounts[i];
|
||||
this.del(layout.P(account, hash));
|
||||
this.put(layout.H(account, tx.height, hash), DUMMY);
|
||||
}
|
||||
|
||||
// Commit the new state. The balance has updated.
|
||||
this.put(layout.R, this.pending.commit());
|
||||
|
||||
this.unlockTX(tx);
|
||||
|
||||
this.emit('tx', tx, details);
|
||||
this.emit('confirmed', tx, details);
|
||||
this.emit('balance', this.pending.toBalance(), details);
|
||||
|
||||
@ -1094,22 +1208,127 @@ TXDB.prototype.confirm = co(function* confirm(tx) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove a transaction from the database. Disconnect inputs.
|
||||
* Recursively remove a transaction
|
||||
* from the database.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype.remove = co(function* remove(hash) {
|
||||
var tx = yield this.getTX(hash);
|
||||
var details;
|
||||
|
||||
if (!tx)
|
||||
return;
|
||||
|
||||
details = yield this.removeRecursive(tx);
|
||||
return yield this.removeRecursive(tx);
|
||||
});
|
||||
|
||||
if (!details)
|
||||
return;
|
||||
/**
|
||||
* Remove a transaction from the
|
||||
* database. Disconnect inputs.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype.erase = co(function* erase(tx) {
|
||||
var hash = tx.hash('hex');
|
||||
var details = new Details(this, tx);
|
||||
var i, path, account, credits;
|
||||
var input, output, coin, credit;
|
||||
|
||||
if (!tx.isCoinbase()) {
|
||||
// We need to undo every part of the
|
||||
// state this transaction ever touched.
|
||||
// Start by getting the undo coins.
|
||||
credits = yield this.getSpentCredits(tx);
|
||||
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
credit = credits[i];
|
||||
|
||||
if (!credit) {
|
||||
// This input never had an undo
|
||||
// coin, but remove it from the
|
||||
// stxo set.
|
||||
this.removeInput(tx, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
coin = credit.coin;
|
||||
path = yield this.getPath(coin);
|
||||
assert(path);
|
||||
|
||||
details.setInput(i, path, coin);
|
||||
|
||||
// Recalculate the balance, remove
|
||||
// from stxo set, remove the undo
|
||||
// coin, and resave the credit.
|
||||
this.pending.unconfirmed += coin.value;
|
||||
|
||||
if (tx.height !== -1) {
|
||||
this.pending.coin++;
|
||||
this.pending.confirmed += coin.value;
|
||||
}
|
||||
|
||||
this.unspendCredit(tx, i);
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to remove all credits
|
||||
// this transaction created.
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
path = yield this.getPath(output);
|
||||
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
details.setOutput(i, path);
|
||||
|
||||
credit = Credit.fromTX(tx, i);
|
||||
|
||||
this.pending.coin--;
|
||||
this.pending.unconfirmed -= output.value;
|
||||
|
||||
if (tx.height !== -1)
|
||||
this.pending.confirmed -= output.value;
|
||||
|
||||
this.removeCredit(credit, path);
|
||||
}
|
||||
|
||||
// Remove the transaction data
|
||||
// itself as well as unindex.
|
||||
this.del(layout.t(hash));
|
||||
this.del(layout.m(tx.ps, hash));
|
||||
|
||||
if (tx.height === -1)
|
||||
this.del(layout.p(hash));
|
||||
else
|
||||
this.del(layout.h(tx.height, hash));
|
||||
|
||||
// Remove all secondary indexing.
|
||||
for (i = 0; i < details.accounts.length; i++) {
|
||||
account = details.accounts[i];
|
||||
|
||||
this.del(layout.T(account, hash));
|
||||
this.del(layout.M(account, tx.ps, hash));
|
||||
|
||||
if (tx.height === -1)
|
||||
this.del(layout.P(account, hash));
|
||||
else
|
||||
this.del(layout.H(account, tx.height, hash));
|
||||
}
|
||||
|
||||
// Update the transaction counter
|
||||
// and commit new state due to
|
||||
// balance change.
|
||||
this.pending.tx--;
|
||||
this.put(layout.R, this.pending.commit());
|
||||
|
||||
this.emit('remove tx', tx, details);
|
||||
this.emit('balance', this.pending.toBalance(), details);
|
||||
|
||||
return details;
|
||||
});
|
||||
@ -1119,7 +1338,7 @@ TXDB.prototype.remove = co(function* remove(hash) {
|
||||
* remove all of its spenders.
|
||||
* @private
|
||||
* @param {TX} tx - Transaction to be removed.
|
||||
* @returns {Promise} - Returns Boolean.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
|
||||
@ -1143,7 +1362,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
|
||||
this.start();
|
||||
|
||||
// Remove the spender.
|
||||
details = yield this._remove(tx);
|
||||
details = yield this.erase(tx);
|
||||
|
||||
assert(details);
|
||||
|
||||
@ -1153,99 +1372,7 @@ TXDB.prototype.removeRecursive = co(function* removeRecursive(tx) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove a transaction from the database. Disconnect inputs.
|
||||
* @private
|
||||
* @param {TX} tx
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype._remove = co(function* remove(tx) {
|
||||
var hash = tx.hash('hex');
|
||||
var details = new Details(this, tx);
|
||||
var i, path, account, credits;
|
||||
var input, output, coin, credit;
|
||||
|
||||
if (!tx.isCoinbase()) {
|
||||
credits = yield this.getSpentCredits(tx);
|
||||
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
input = tx.inputs[i];
|
||||
credit = credits[i];
|
||||
|
||||
if (!credit) {
|
||||
this.removeInput(tx, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
coin = credit.coin;
|
||||
path = yield this.getPath(coin);
|
||||
assert(path);
|
||||
|
||||
details.setInput(i, path, coin);
|
||||
|
||||
this.pending.unconfirmed += coin.value;
|
||||
|
||||
if (tx.height !== -1) {
|
||||
this.pending.coin++;
|
||||
this.pending.confirmed += coin.value;
|
||||
}
|
||||
|
||||
this.unspendCredit(tx, i);
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
path = yield this.getPath(output);
|
||||
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
details.setOutput(i, path);
|
||||
|
||||
credit = Credit.fromTX(tx, i);
|
||||
|
||||
this.pending.coin--;
|
||||
this.pending.unconfirmed -= output.value;
|
||||
|
||||
if (tx.height !== -1)
|
||||
this.pending.confirmed -= output.value;
|
||||
|
||||
this.removeCredit(credit, path);
|
||||
}
|
||||
|
||||
this.del(layout.t(hash));
|
||||
this.del(layout.m(tx.ps, hash));
|
||||
|
||||
if (tx.height === -1)
|
||||
this.del(layout.p(hash));
|
||||
else
|
||||
this.del(layout.h(tx.height, hash));
|
||||
|
||||
for (i = 0; i < details.accounts.length; i++) {
|
||||
account = details.accounts[i];
|
||||
|
||||
this.del(layout.T(account, hash));
|
||||
this.del(layout.M(account, tx.ps, hash));
|
||||
|
||||
if (tx.height === -1)
|
||||
this.del(layout.P(account, hash));
|
||||
else
|
||||
this.del(layout.H(account, tx.height, hash));
|
||||
}
|
||||
|
||||
this.pending.tx--;
|
||||
this.put(layout.R, this.pending.commit());
|
||||
|
||||
this.emit('remove tx', tx, details);
|
||||
this.emit('balance', this.pending.toBalance(), details);
|
||||
|
||||
return details;
|
||||
});
|
||||
|
||||
/**
|
||||
* Unconfirm a transaction. This is usually necessary after a reorg.
|
||||
* Unconfirm a transaction. Necessary after a reorg.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
@ -1268,7 +1395,7 @@ TXDB.prototype.unconfirm = co(function* unconfirm(hash) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Unconfirm a transaction without a lock.
|
||||
* Unconfirm a transaction without a batch.
|
||||
* @private
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
@ -1280,16 +1407,16 @@ TXDB.prototype._unconfirm = co(function* unconfirm(hash) {
|
||||
if (!tx)
|
||||
return;
|
||||
|
||||
return yield this.__unconfirm(tx);
|
||||
return yield this.disconnect(tx);
|
||||
});
|
||||
|
||||
/**
|
||||
* Unconfirm a transaction. This is usually necessary after a reorg.
|
||||
* Unconfirm a transaction. Necessary after a reorg.
|
||||
* @param {Hash} hash
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
TXDB.prototype.disconnect = co(function* disconnect(tx) {
|
||||
var hash = tx.hash('hex');
|
||||
var details = new Details(this, tx);
|
||||
var height = tx.height;
|
||||
@ -1301,6 +1428,9 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
tx.unsetBlock();
|
||||
|
||||
if (!tx.isCoinbase()) {
|
||||
// We need to reconnect the coins. Start
|
||||
// by getting all of the undo coins we know
|
||||
// about.
|
||||
credits = yield this.getSpentCredits(tx);
|
||||
|
||||
for (i = 0; i < tx.inputs.length; i++) {
|
||||
@ -1322,12 +1452,15 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
this.pending.coin++;
|
||||
this.pending.confirmed += coin.value;
|
||||
|
||||
// Resave the credit and mark it
|
||||
// as spent in the mempool instead.
|
||||
credit.spent = true;
|
||||
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to remove heights on
|
||||
// the credits and undo coins.
|
||||
for (i = 0; i < tx.outputs.length; i++) {
|
||||
output = tx.outputs[i];
|
||||
path = yield this.getPath(output);
|
||||
@ -1337,6 +1470,13 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
|
||||
credit = yield this.getCredit(hash, i);
|
||||
|
||||
assert(credit);
|
||||
assert(!credit.spent);
|
||||
|
||||
// Neither of the spent cases below
|
||||
// should ever happen, but account
|
||||
// for them if some jackass is messing
|
||||
// around with bcoin.
|
||||
if (!credit) {
|
||||
yield this.updateSpentCoin(tx, i);
|
||||
continue;
|
||||
@ -1347,6 +1487,8 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
|
||||
details.setOutput(i, path);
|
||||
|
||||
// Update coin height and confirmed
|
||||
// balance. Save once again.
|
||||
coin = credit.coin;
|
||||
coin.height = -1;
|
||||
|
||||
@ -1356,16 +1498,22 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) {
|
||||
this.saveCredit(credit, path);
|
||||
}
|
||||
|
||||
// We need to update the now-removed
|
||||
// block properties and reindex due
|
||||
// to the height change.
|
||||
this.put(layout.t(hash), tx.toExtended());
|
||||
this.put(layout.p(hash), DUMMY);
|
||||
this.del(layout.h(height, hash));
|
||||
|
||||
// Secondary indexing also needs to change.
|
||||
for (i = 0; i < details.accounts.length; i++) {
|
||||
account = details.accounts[i];
|
||||
this.put(layout.P(account, hash), DUMMY);
|
||||
this.del(layout.H(account, height, hash));
|
||||
}
|
||||
|
||||
// Commit state due to unconfirmed
|
||||
// vs. confirmed balance change.
|
||||
this.put(layout.R, this.pending.commit());
|
||||
|
||||
this.emit('unconfirmed', tx, details);
|
||||
@ -2443,8 +2591,10 @@ TXDB.prototype.zap = co(function* zap(account, age) {
|
||||
|
||||
TXDB.prototype.abandon = co(function* abandon(hash) {
|
||||
var result = yield this.has(layout.p(hash));
|
||||
|
||||
if (!result)
|
||||
throw new Error('TX not eligible.');
|
||||
|
||||
return yield this.remove(hash);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user