diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 38c3ae98..fb5dc6fb 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -136,12 +136,6 @@ 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); }, @@ -216,7 +210,6 @@ function TXDB(wallet) { this.network = wallet.db.network; this.options = wallet.db.options; this.coinCache = new LRU(10000); - this.spentCache = new LRU(10000); this.locked = {}; this.state = null; @@ -700,7 +693,7 @@ TXDB.prototype._add = co(function* add(tx) { var hash = tx.hash('hex'); var path, account, existing; var i, input, output, coin; - var prevout, key, spender, raw, details; + var prevout, key, spender, raw, credit, details; assert(!tx.mutable, 'Cannot add mutable TX to wallet.'); @@ -755,12 +748,13 @@ TXDB.prototype._add = co(function* add(tx) { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; prevout = input.prevout; - coin = yield this.getCoin(prevout.hash, prevout.index); + credit = yield this.getCredit(prevout.hash, prevout.index); // Only bother if this input is ours. - if (!coin) + if (!credit) continue; + coin = credit.coin; path = yield this.getPath(coin); assert(path); @@ -775,8 +769,10 @@ TXDB.prototype._add = co(function* add(tx) { this.pending.unconfirmed -= coin.value; if (tx.height === -1) { - this.put(layout.S(prevout.hash, prevout.index), spender); - this.spentCache.set(key, spender); + credit.spent = true; + raw = credit.toRaw(); + this.put(layout.c(prevout.hash, prevout.index), raw); + this.coinCache.set(key, raw); } else { this.pending.confirmed -= coin.value; this.del(layout.c(prevout.hash, prevout.index)); @@ -802,8 +798,9 @@ TXDB.prototype._add = co(function* add(tx) { details.addOutput(i, path); - coin = Coin.fromTX(tx, i); - raw = coin.toRaw(); + credit = Credit.fromTX(tx, i); + coin = credit.coin; + raw = credit.toRaw(); this.pending.unconfirmed += coin.value; @@ -978,28 +975,6 @@ TXDB.prototype.getSpent = co(function* getSpent(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.getSpending = co(function* getSpending(hash, index) { - var key = hash + index; - var data = this.spentCache.get(key); - - if (data) - return Outpoint.fromRaw(data); - - data = yield this.get(layout.S(hash, index)); - - if (!data) - return; - - return Outpoint.fromRaw(data); -}); - /** * Test a whether a coin has been spent. * @param {Hash} hash @@ -1012,25 +987,6 @@ TXDB.prototype.isSpent = co(function* isSpent(hash, index) { return data != null; }); -/** - * 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 = hash + index; - var data = this.spentCache.get(key); - - if (data) - return true; - - data = yield this.get(layout.S(hash, index)); - - return data != null; -}); - /** * Attempt to confirm a transaction. * @private @@ -1044,7 +1000,7 @@ TXDB.prototype.confirm = co(function* confirm(tx, existing) { var hash = tx.hash('hex'); var i, account, output, coin; var input, prevout, path, spender, coins; - var key, raw, details; + var key, raw, credit, details; // Inject block properties. existing.ts = tx.ts; @@ -1095,14 +1051,12 @@ TXDB.prototype.confirm = co(function* confirm(tx, existing) { key = prevout.hash + prevout.index; - 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.pending.coin--; this.pending.confirmed -= coin.value; - this.spentCache.remove(key); this.coinCache.remove(key); } } @@ -1121,13 +1075,14 @@ TXDB.prototype.confirm = co(function* confirm(tx, existing) { // Update spent coin. yield this.updateSpentCoin(tx, i); - coin = yield this.getCoin(hash, i); + credit = yield this.getCredit(hash, i); - if (!coin) + if (!credit) continue; + coin = credit.coin; coin.height = tx.height; - raw = coin.toRaw(); + raw = credit.toRaw(); this.pending.confirmed += coin.value; this.pending.coin++; @@ -1226,7 +1181,7 @@ TXDB.prototype.lazyRemove = co(function* lazyRemove(tx) { TXDB.prototype.__remove = co(function* remove(tx) { var hash = tx.hash('hex'); var i, path, account, key, prevout; - var input, output, coin, coins, raw, details; + var input, output, coin, coins, raw, credit, details; details = new Details(this, tx); @@ -1254,6 +1209,8 @@ TXDB.prototype.__remove = co(function* remove(tx) { path = yield this.getPath(coin); assert(path); + credit = new Credit(coin); + details.addInput(i, path, coin); this.pending.unconfirmed += coin.value; @@ -1261,15 +1218,17 @@ TXDB.prototype.__remove = co(function* remove(tx) { this.del(layout.s(prevout.hash, prevout.index)); if (tx.height !== -1) { - raw = coin.toRaw(); + raw = credit.toRaw(); this.pending.confirmed += coin.value; this.pending.coin++; this.put(layout.c(prevout.hash, prevout.index), raw); this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); this.coinCache.set(key, raw); } else { - this.del(layout.S(prevout.hash, prevout.index)); - this.spentCache.remove(key); + credit.spent = false; + raw = credit.toRaw(); + this.put(layout.c(prevout.hash, prevout.index), raw); + this.coinCache.set(key, raw); } this.del(layout.d(hash, i)); @@ -1372,7 +1331,7 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) { var hash = tx.hash('hex'); var height = tx.height; var i, account, output, key, coin, coins; - var input, prevout, path, spender, raw; + var input, prevout, path, spender, raw, credit; if (height === -1) return; @@ -1401,7 +1360,7 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) { assert(coin.height !== -1); - raw = coin.toRaw(); + credit = new Credit(coin); path = yield this.getPath(coin); assert(path); @@ -1412,14 +1371,14 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) { spender = Outpoint.fromTX(tx, i).toRaw(); - this.put(layout.S(prevout.hash, prevout.index), spender); + credit.spent = true; + raw = credit.toRaw(); this.put(layout.c(prevout.hash, prevout.index), raw); this.put(layout.C(path.account, prevout.hash, prevout.index), DUMMY); this.pending.coin++; this.pending.confirmed += coin.value; - this.spentCache.set(key, spender); this.coinCache.set(key, raw); } } @@ -1432,15 +1391,16 @@ TXDB.prototype.__unconfirm = co(function* unconfirm(tx) { // Update spent coin. yield this.updateSpentCoin(tx, i); - coin = yield this.getCoin(hash, i); + credit = yield this.getCredit(hash, i); - if (!coin) + if (!credit) continue; details.addOutput(i, path); + coin = credit.coin; coin.height = -1; - raw = coin.toRaw(); + raw = credit.toRaw(); this.pending.confirmed -= coin.value; this.pending.coin++; @@ -1939,39 +1899,58 @@ TXDB.prototype.getPending = co(function* getPending(account) { */ TXDB.prototype.getCoins = co(function* getCoins(account, all) { + var credits = yield this.getCredits(account, all); + var coins = []; + var i, credit; + + for (i = 0; i < credits.length; i++) { + credit = credits[i]; + coins.push(credit.coin); + } + + return coins; +}); + +/** + * Get coins. + * @param {Number?} account + * @returns {Promise} - Returns {@link Coin}[]. + */ + +TXDB.prototype.getCredits = co(function* getCredits(account, all) { var self = this; var out = []; - var i, coins, coin; + var i, credits, credit; // Slow case if (account != null) - return this.getAccountCoins(account, all); + return this.getAccountCredits(account, all); // Fast case - coins = yield this.range({ + credits = yield this.range({ gte: layout.c(constants.NULL_HASH, 0x00000000), lte: layout.c(constants.HIGH_HASH, 0xffffffff), parse: function(key, value) { var parts = layout.cc(key); var hash = parts[0]; var index = parts[1]; - var coin = Coin.fromRaw(value); + var credit = Credit.fromRaw(value); var ckey = hash + index; - coin.hash = hash; - coin.index = index; + credit.coin.hash = hash; + credit.coin.index = index; self.coinCache.set(ckey, value); - return coin; + return credit; } }); if (all) - return coins; + return credits; - for (i = 0; i < coins.length; i++) { - coin = coins[i]; - if (yield this.isSpending(coin.hash, coin.index)) + for (i = 0; i < credits.length; i++) { + credit = credits[i]; + if (credit.spent) continue; - out.push(coin); + out.push(credit); } return out; @@ -1984,28 +1963,47 @@ TXDB.prototype.getCoins = co(function* getCoins(account, all) { */ TXDB.prototype.getAccountCoins = co(function* getAccountCoins(account, all) { - var prevout = yield this.getOutpoints(account); + var credits = yield this.getAccountCredits(account, all); var coins = []; - var i, op, coin; + var i, credit; - for (i = 0; i < prevout.length; i++) { - op = prevout[i]; - coin = yield this.getCoin(op.hash, op.index); - - if (!coin) - continue; - - if (!all) { - if (yield this.isSpending(coin.hash, coin.index)) - continue; - } - - coins.push(coin); + for (i = 0; i < credits.length; i++) { + credit = credits[i]; + coins.push(credit.coin); } return coins; }); +/** + * Get coins by account. + * @param {Number} account + * @returns {Promise} - Returns {@link Coin}[]. + */ + +TXDB.prototype.getAccountCredits = co(function* getAccountCredits(account, all) { + var prevout = yield this.getOutpoints(account); + var credits = []; + var i, op, credit; + + for (i = 0; i < prevout.length; i++) { + op = prevout[i]; + credit = yield this.getCredit(op.hash, op.index); + + if (!credit) + continue; + + if (!all) { + if (credit.spent) + continue; + } + + credits.push(credit); + } + + return credits; +}); + /** * Fill a transaction with coins (all historical coins). * @param {TX} tx @@ -2046,7 +2044,7 @@ TXDB.prototype.fillHistory = co(function* fillHistory(tx) { */ TXDB.prototype.fillCoins = co(function* fillCoins(tx) { - var i, input, prevout, coin; + var i, input, prevout, credit; if (tx.isCoinbase()) return tx; @@ -2058,12 +2056,12 @@ TXDB.prototype.fillCoins = co(function* fillCoins(tx) { if (input.coin) continue; - coin = yield this.getCoin(prevout.hash, prevout.index); + credit = yield this.getCredit(prevout.hash, prevout.index); - if (!coin) + if (!credit) continue; - if (yield this.isSpending(coin.hash, coin.index)) + if (credit.spent) continue; input.coin = coin; @@ -2180,15 +2178,29 @@ TXDB.prototype.hasTX = function hasTX(hash) { */ TXDB.prototype.getCoin = co(function* getCoin(hash, index) { + var credit = yield this.getCredit(hash, index); + if (!credit) + return; + return credit.coin; +}); + +/** + * Get coin. + * @param {Hash} hash + * @param {Number} index + * @returns {Promise} - Returns {@link Coin}. + */ + +TXDB.prototype.getCredit = co(function* getCredit(hash, index) { var key = hash + index; var data = this.coinCache.get(key); - var coin; + var credit; if (data) { - coin = Coin.fromRaw(data); - coin.hash = hash; - coin.index = index; - return coin; + credit = Credit.fromRaw(data); + credit.coin.hash = hash; + credit.coin.index = index; + return credit; } data = yield this.get(layout.c(hash, index)); @@ -2196,13 +2208,13 @@ TXDB.prototype.getCoin = co(function* getCoin(hash, index) { if (!data) return; - coin = Coin.fromRaw(data); - coin.hash = hash; - coin.index = index; + credit = Credit.fromRaw(data); + credit.coin.hash = hash; + credit.coin.index = index; this.coinCache.set(key, data); - return coin; + return credit; }); /** @@ -2288,18 +2300,18 @@ TXDB.prototype.getBalance = co(function* getBalance(account) { */ TXDB.prototype.getWalletBalance = co(function* getWalletBalance() { - var coins = yield this.getCoins(null, true); + var credits = yield this.getCredits(null, true); var balance = new Balance(this.wallet.wid, this.wallet.id, -1); - var i, coin; + var i, credit; - for (i = 0; i < coins.length; i++) { - coin = coins[i]; + for (i = 0; i < credits.length; i++) { + credit = credits[i]; - if (coin.height !== -1) - balance.confirmed += coin.value; + if (credit.coin.height !== -1) + balance.confirmed += credit.coin.value; - if (!(yield this.isSpending(coin.hash, coin.index))) - balance.unconfirmed += coin.value; + if (!credit.spent) + balance.unconfirmed += credit.coin.value; } return balance; @@ -2312,18 +2324,18 @@ TXDB.prototype.getWalletBalance = co(function* getWalletBalance() { */ TXDB.prototype.getAccountBalance = co(function* getAccountBalance(account) { - var coins = yield this.getAccountCoins(account, true); + var credits = yield this.getAccountCredits(account, true); var balance = new Balance(this.wallet.wid, this.wallet.id, account); - var i, coin; + var i, credit; - for (i = 0; i < coins.length; i++) { - coin = coins[i]; + for (i = 0; i < credits.length; i++) { + credit = credits[i]; - if (coin.height !== -1) - balance.confirmed += coin.value; + if (credit.coin.height !== -1) + balance.confirmed += credit.coin.value; - if (!(yield this.isSpending(coin.hash, coin.index))) - balance.unconfirmed += coin.value; + if (!credit.spent) + balance.unconfirmed += credit.coin.value; } return balance; @@ -2651,6 +2663,45 @@ DetailsMember.prototype.toJSON = function toJSON(network) { }; }; +function Credit(coin, spent) { + if (!(this instanceof Credit)) + return new Credit(coin, spent); + this.coin = coin || new Coin(); + this.spent = spent || false; +} + +Credit.prototype.fromRaw = function fromRaw(data) { + var p = BufferReader(data); + this.coin.fromRaw(p); + this.spent = p.readU8() === 1; + return this; +}; + +Credit.fromRaw = function fromRaw(data) { + return new Credit().fromRaw(data); +}; + +Credit.prototype.toRaw = function toRaw(writer) { + var p = BufferWriter(writer); + + this.coin.toRaw(p); + p.writeU8(this.spent ? 1 : 0); + + if (!writer) + p = p.render(); + + return p; +}; + +Credit.prototype.fromTX = function fromTX(tx, i) { + this.coin.fromTX(tx, i); + this.spent = false; + return this; +}; + +Credit.fromTX = function fromTX(tx, i) { + return new Credit().fromTX(tx, i); +}; /* * Expose