diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 4d9e5c87..4593289f 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -136,6 +136,12 @@ var layout = { ss: function ss(key) { return layout.hii(key); }, + S: function S(hash, index) { + return layout.hi(0x53, hash, index); + }, + Ss: function Ss(key) { + return layout.hii(key); + }, p: function p(hash) { return layout.ha(0x70, hash); }, @@ -206,7 +212,7 @@ function TXDB(wallet) { this.logger = wallet.db.logger; this.network = wallet.db.network; this.options = wallet.db.options; - this.coinCache = new LRU(10000); + this.coinCache = new LRU.Nil(10000); this.locked = {}; this.state = null; @@ -750,10 +756,16 @@ TXDB.prototype._add = co(function* add(tx, info) { key = prevout.hash + prevout.index; spender = Outpoint.fromTX(tx, i).toRaw(); + this.put(layout.s(prevout.hash, prevout.index), spender); - this.del(layout.c(prevout.hash, prevout.index)); - this.del(layout.C(path.account, prevout.hash, prevout.index)); + if (tx.height === -1) { + this.put(layout.S(prevout.hash, prevout.index), spender); + } else { + this.del(layout.c(prevout.hash, prevout.index)); + this.del(layout.C(path.account, prevout.hash, prevout.index)); + } + this.put(layout.d(hash, i), coin.toRaw()); this.pending.sub(coin); @@ -908,6 +920,23 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { return Outpoint.fromRaw(data); }); +/** + * Test a whether a coin has been spent. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns Boolean. + */ + +TXDB.prototype.isSpending = co(function* isSpending(hash, index) { + var key = layout.S(hash, index); + var data = yield this.get(key); + + if (!data) + return; + + return Outpoint.fromRaw(data); +}); + /** * Attempt to confirm a transaction. * @private @@ -921,7 +950,8 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { TXDB.prototype.confirm = co(function* confirm(tx, info) { var hash = tx.hash('hex'); var i, account, existing, output, coin; - var address, key; + var input, prevout, path, spender, coins; + var address, key, spent; existing = yield this.getTX(hash); @@ -956,6 +986,41 @@ TXDB.prototype.confirm = co(function* confirm(tx, info) { this.put(layout.H(account, tx.height, hash), DUMMY); } + // Consume unspent money or add orphans + if (!tx.isCoinbase()) { + coins = yield this.fillHistory(tx); + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + coin = coins[i]; + + if (!coin) { + coin = yield this.getCoin(prevout.hash, prevout.index); + + if (!coin) + continue; + + this.put(layout.d(hash, i), coin.toRaw()); + this.pending.sub(coin); + } + + // Only bother if this input is ours. + path = info.getPath(coin); + assert(path); + + key = prevout.hash + prevout.index; + + spender = Outpoint.fromTX(tx, i).toRaw(); + + this.del(layout.S(prevout.hash, prevout.index)); + this.del(layout.c(prevout.hash, prevout.index)); + this.del(layout.C(path.account, prevout.hash, prevout.index)); + + this.coinCache.remove(key); + } + } + for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; key = hash + i; @@ -964,13 +1029,16 @@ TXDB.prototype.confirm = co(function* confirm(tx, info) { if (!info.hasPath(output)) continue; - coin = yield this.getCoin(hash, i); + spent = yield this.isSpending(prevout.hash, prevout.index); // Update spent coin. - if (!coin) { - yield this.updateSpentCoin(tx, i); + if (spent) + yield this.updateSpentCoin(spent, tx, i); + + coin = yield this.getCoin(hash, i); + + if (!coin) continue; - } this.pending.confirm(coin.value); @@ -1113,10 +1181,13 @@ TXDB.prototype.__remove = co(function* remove(tx, info) { coin = coin.toRaw(); - this.put(layout.c(prevout.hash, prevout.index), coin); - this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); + if (tx.height !== -1) { + this.put(layout.c(prevout.hash, prevout.index), coin); + this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); + } else { + this.del(layout.S(prevout.hash, prevout.index)); + } this.del(layout.d(hash, i)); - this.del(layout.s(prevout.hash, prevout.index)); this.coinCache.set(key, coin); } @@ -1224,6 +1295,35 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx, info) { this.del(layout.H(account, height, hash)); } + var input, prevout, coin, path, spender; + // Consume unspent money or add orphans + if (!tx.isCoinbase()) { + var coins = yield this.fillHistory(tx); + + for (i = 0; i < tx.inputs.length; i++) { + input = tx.inputs[i]; + prevout = input.prevout; + coin = coins[i]; + + // Only bother if this input is ours. + if (!coin) + continue; + + path = info.getPath(coin); + assert(path); + + key = prevout.hash + prevout.index; + + spender = Outpoint.fromTX(tx, i).toRaw(); + + this.put(layout.S(prevout.hash, prevout.index), spender); + this.put(layout.c(prevout.hash, prevout.index), coin.toRaw()); + this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); + + this.coinCache.remove(key); + } + } + for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; key = hash + i; @@ -1231,7 +1331,8 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx, info) { // Update spent coin. if (!coin) { - yield this.updateSpentCoin(tx, i); + var spent = yield this.isSpent(prevout.hash, prevout.index); + yield this.updateSpentCoin(spent, tx, i); continue; } @@ -1727,15 +1828,17 @@ TXDB.prototype.getPending = co(function* getPending(account) { * @returns {Promise} - Returns {@link Coin}[]. */ -TXDB.prototype.getCoins = function getCoins(account) { +TXDB.prototype.getCoins = co(function* getCoins(account) { var self = this; + var out = []; + var i, coins, coin; // Slow case if (account != null) return this.getAccountCoins(account); // Fast case - return this.range({ + coins = yield this.range({ gte: layout.c(constants.NULL_HASH, 0x00000000), lte: layout.c(constants.HIGH_HASH, 0xffffffff), parse: function(key, value) { @@ -1750,7 +1853,16 @@ TXDB.prototype.getCoins = function getCoins(account) { return coin; } }); -}; + + for (i = 0; i < coins.length; i++) { + coin = coins[i]; + if (yield this.isSpending(coin.hash, coin.index)) + continue; + out.push(coin); + } + + return out; +}); /** * Get coins by account. @@ -1988,14 +2100,13 @@ TXDB.prototype.getSpentCoin = co(function* getSpentCoin(spent, prevout) { * @returns {Promise} */ -TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(tx, i) { - var prevout = Outpoint.fromTX(tx, i); - var spent = yield this.isSpent(prevout.hash, prevout.index); - var coin; +TXDB.prototype.updateSpentCoin = co(function* updateSpentCoin(spent, tx, i) { + var prevout, coin; if (!spent) return; + prevout = Outpoint.fromTX(tx, i); coin = yield this.getSpentCoin(spent, prevout); if (!coin)