diff --git a/lib/wallet/txdb.js b/lib/wallet/txdb.js index 4c34d08f..261fc3c1 100644 --- a/lib/wallet/txdb.js +++ b/lib/wallet/txdb.js @@ -17,6 +17,7 @@ var BufferWriter = require('../utils/writer'); var TX = require('../primitives/tx'); var Coin = require('../primitives/coin'); var Outpoint = require('../primitives/outpoint'); +var WalletBlock = require('./walletblock'); var DUMMY = new Buffer([0]); /* @@ -857,10 +858,15 @@ TXDB.prototype.getSpent = co(function* getSpent(hash, index) { * @returns {Promise} - Returns Boolean. */ -TXDB.prototype.isSpent = co(function* isSpent(hash, index) { - var data = yield this.get(layout.s(hash, index)); - return data != null; -}); +TXDB.prototype.isSpent = function isSpent(hash, index) { + return this.has(layout.s(hash, index)); +}; + +/** + * Append to the global tx record. + * @param {TX} tx + * @returns {Promise} + */ TXDB.prototype.addTXRecord = co(function* addTXRecord(tx) { var hash = tx.hash('hex'); @@ -878,6 +884,12 @@ TXDB.prototype.addTXRecord = co(function* addTXRecord(tx) { this.walletdb.writeTX(this.wallet, hash, wids); }); +/** + * Remove from the global tx record. + * @param {TX} tx + * @returns {Promise} + */ + TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) { var hash = tx.hash('hex'); var wids = yield this.walletdb.getWalletsByTX(hash); @@ -893,6 +905,7 @@ TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) { if (tx.height !== -1) { block = yield this.walletdb.getBlock(tx.height); + assert(block); if (block.remove(hash, this.wallet.wid)) this.walletdb.writeBlock(this.wallet, block); @@ -906,12 +919,19 @@ TXDB.prototype.removeTXRecord = co(function* removeTXRecord(tx) { this.walletdb.writeTX(this.wallet, hash, wids); }); -TXDB.prototype.addBlockRecord = co(function* addBlockRecord(tx, record) { +/** + * Append to the global block record. + * @param {TX} tx + * @param {Number} height + * @returns {Promise} + */ + +TXDB.prototype.addBlockRecord = co(function* addBlockRecord(tx, height) { var hash = tx.hash('hex'); - var block = yield this.walletdb.getBlock(record.height); + var block = yield this.walletdb.getBlock(height); if (!block) - block = record.clone(); + block = new WalletBlock(tx.block, tx.height, tx.ts); if (!block.add(hash, this.wallet.wid)) return; @@ -919,6 +939,13 @@ TXDB.prototype.addBlockRecord = co(function* addBlockRecord(tx, record) { this.walletdb.writeBlock(this.wallet, block); }); +/** + * Remove from the global block record. + * @param {TX} + * @param {Number} height + * @returns {Promise} + */ + TXDB.prototype.removeBlockRecord = co(function* removeBlockRecord(tx, height) { var hash = tx.hash('hex'); var block = yield this.walletdb.getBlock(height); @@ -985,6 +1012,9 @@ TXDB.prototype._add = co(function* add(tx, block) { return; // Save the index (can't get this elsewhere). + existing.height = tx.height; + existing.block = tx.block; + existing.ts = tx.ts; existing.index = tx.index; // Confirm transaction. @@ -1026,7 +1056,7 @@ TXDB.prototype._add = co(function* add(tx, block) { TXDB.prototype.insert = co(function* insert(tx, block) { var hash = tx.hash('hex'); - var height = block ? block.height : this.walletdb.height; + var height = this.walletdb.height + 1; var details = new Details(this, tx, height); var updated = false; var i, input, output, coin; @@ -1164,8 +1194,8 @@ TXDB.prototype.insert = co(function* insert(tx, block) { yield this.addTXRecord(tx); - if (block) - yield this.addBlockRecord(tx, block); + if (tx.height !== -1) + yield this.addBlockRecord(tx, tx.height); // Update the transaction counter and // commit the new state. This state will @@ -1226,18 +1256,11 @@ TXDB.prototype.confirm = co(function* confirm(hash, block) { TXDB.prototype._confirm = co(function* confirm(tx, block) { var hash = tx.hash('hex'); - var height = block ? block.height : this.walletdb.height; + var height = this.walletdb.height + 1; var details = new Details(this, tx, height); var i, account, output, coin, input, prevout; var path, credit, credits; - if (!block) - block = this.wallet.db.tip; - - tx.height = block.height; - tx.block = block.hash; - tx.ts = block.ts; - if (!tx.isCoinbase()) { credits = yield this.getSpentCredits(tx); @@ -1332,8 +1355,8 @@ TXDB.prototype._confirm = co(function* confirm(tx, block) { yield this.addTXRecord(tx); - if (block) - yield this.addBlockRecord(tx, block); + if (tx.height !== -1) + yield this.addBlockRecord(tx, tx.height); // Commit the new state. The balance has updated. this.put(layout.R, this.pending.commit()); @@ -1558,7 +1581,7 @@ TXDB.prototype._unconfirm = co(function* unconfirm(hash, block) { TXDB.prototype.disconnect = co(function* disconnect(tx, block) { var hash = tx.hash('hex'); - var details = new Details(this, tx, block.height - 1); + var details = new Details(this, tx, this.walletdb.height - 1); var height = tx.height; var i, account, output, coin, credits; var input, path, credit; @@ -1630,7 +1653,7 @@ TXDB.prototype.disconnect = co(function* disconnect(tx, block) { this.saveCredit(credit, path); } - yield this.removeBlockRecord(tx, block.height); + yield this.removeBlockRecord(tx, height); // We need to update the now-removed // block properties and reindex due diff --git a/lib/wallet/walletblock.js b/lib/wallet/walletblock.js new file mode 100644 index 00000000..3a75beda --- /dev/null +++ b/lib/wallet/walletblock.js @@ -0,0 +1,310 @@ +/*! + * walletdb.js - storage for wallets + * Copyright (c) 2014-2015, Fedor Indutny (MIT License) + * Copyright (c) 2014-2016, Christopher Jeffrey (MIT License). + * https://github.com/bcoin-org/bcoin + */ + +'use strict'; + +var utils = require('../utils/utils'); +var assert = require('assert'); +var constants = require('../protocol/constants'); +var BufferReader = require('../utils/reader'); +var BufferWriter = require('../utils/writer'); + +/** + * Wallet Block + * @constructor + * @param {Hash} hash + * @param {Number} height + */ + +function WalletBlock(hash, height, ts) { + if (!(this instanceof WalletBlock)) + return new WalletBlock(hash, height, ts); + + this.hash = hash || constants.NULL_HASH; + this.height = height != null ? height : -1; + this.ts = ts || 0; + this.txs = []; + this.index = {}; +} + +/** + * Clone the block. + * @returns {WalletBlock} + */ + +WalletBlock.prototype.clone = function clone() { + return new WalletBlock(this.hash, this.height, this.ts); +}; + +/** + * Instantiate wallet block from chain entry. + * @private + * @param {ChainEntry} entry + */ + +WalletBlock.prototype.fromEntry = function fromEntry(entry) { + this.hash = entry.hash; + this.height = entry.height; + this.ts = entry.ts; + return this; +}; + +/** + * Instantiate wallet block from json object. + * @private + * @param {Object} json + */ + +WalletBlock.prototype.fromJSON = function fromJSON(json) { + this.hash = utils.revHex(json.hash); + this.height = json.height; + this.ts = json.ts; + return this; +}; + +/** + * Instantiate wallet block from serialized data. + * @private + * @param {Hash} hash + * @param {Buffer} data + */ + +WalletBlock.prototype.fromRaw = function fromRaw(data) { + var p = new BufferReader(data); + var i, hash, tx, count; + + this.hash = p.readHash('hex'); + this.height = p.readU32(); + this.ts = p.readU32(); + + while (p.left()) { + hash = p.readHash('hex'); + tx = new TXHash(hash); + count = p.readVarint(); + for (i = 0; i < count; i++) + tx.wids.push(p.readU32()); + this.txs.push(tx); + this.index[tx.hash] = tx; + } + + return this; +}; + +/** + * Instantiate wallet block from serialized tip data. + * @private + * @param {Buffer} data + */ + +WalletBlock.prototype.fromTip = function fromTip(data) { + var p = new BufferReader(data); + this.hash = p.readHash('hex'); + this.height = p.readU32(); + this.ts = p.readU32(); + return this; +}; + +/** + * Instantiate wallet block from chain entry. + * @param {ChainEntry} entry + * @returns {WalletBlock} + */ + +WalletBlock.fromEntry = function fromEntry(entry) { + return new WalletBlock().fromEntry(entry); +}; + +/** + * Instantiate wallet block from json object. + * @param {Object} json + * @returns {WalletBlock} + */ + +WalletBlock.fromJSON = function fromJSON(json) { + return new WalletBlock().fromJSON(json); +}; + +/** + * Instantiate wallet block from serialized data. + * @param {Hash} hash + * @param {Buffer} data + * @returns {WalletBlock} + */ + +WalletBlock.fromRaw = function fromRaw(data) { + return new WalletBlock().fromRaw(data); +}; + +/** + * Instantiate wallet block from serialized tip data. + * @private + * @param {Buffer} data + */ + +WalletBlock.fromTip = function fromTip(data) { + return new WalletBlock().fromTip(data); +}; + +/** + * Serialize the wallet block as a tip (hash and height). + * @returns {Buffer} + */ + +WalletBlock.prototype.toTip = function toTip(writer) { + var p = new BufferWriter(writer); + + p.writeHash(this.hash); + p.writeU32(this.height); + p.writeU32(this.ts); + + if (!writer) + p = p.render(); + + return p; +}; + +/** + * Serialize the wallet block as a block. + * Contains matching transaction hashes. + * @returns {Buffer} + */ + +WalletBlock.prototype.toRaw = function toRaw(writer) { + var p = new BufferWriter(writer); + var i, j, tx; + + p.writeHash(this.hash); + p.writeU32(this.height); + p.writeU32(this.ts); + + for (i = 0; i < this.txs.length; i++) { + tx = this.txs[i]; + p.writeHash(tx.hash); + p.writeVarint(tx.wids.length); + for (j = 0; j < tx.wids.length; j++) + p.writeU32(tx.wids[j]); + } + + if (!writer) + p = p.render(); + + return p; +}; + +/** + * Add a hash and wid pair to the block. + * @param {Hash} hash + * @param {WalletID} wid + * @returns {Boolean} + */ + +WalletBlock.prototype.add = function add(hash, wid) { + var tx = this.index[hash]; + + if (!tx) { + tx = new TXHash(hash); + tx.wids.push(wid); + this.txs.push(tx); + this.index[tx.hash] = tx; + return true; + } + + return tx.add(wid); +}; + +/** + * Remove a hash and wid pair from the block. + * @param {Hash} hash + * @param {WalletID} wid + * @returns {Boolean} + */ + +WalletBlock.prototype.remove = function remove(hash, wid) { + var tx = this.index[hash]; + var result; + + if (!tx) + return false; + + if (!tx.remove(wid)) + return false; + + if (tx.wids.length === 0) { + result = utils.binaryRemove(this.txs, tx, cmpid); + assert(result); + delete this.index[tx.hash]; + } + + return true; +}; + +/** + * Convert the block to a more json-friendly object. + * @returns {Object} + */ + +WalletBlock.prototype.toJSON = function toJSON() { + return { + hash: utils.revHex(this.hash), + height: this.height + }; +}; + +/** + * TX Hash + * @constructor + */ + +function TXHash(hash, wids) { + this.hash = hash || constants.NULL_HASH; + this.wids = wids || []; + this.id = TXHash.id++; +} + +TXHash.id = 0; + +TXHash.prototype.add = function add(wid) { + return utils.binaryInsert(this.wids, wid, cmp, true) !== -1; +}; + +TXHash.prototype.remove = function remove(wid) { + return utils.binaryRemove(this.wids, wid, cmp); +}; + +TXHash.prototype.toRaw = function toRaw(writer) { + return serializeWallets(this.wids, writer); +}; + +TXHash.prototype.fromRaw = function fromRaw(data) { + return parseWallets(data); +}; + +TXHash.fromRaw = function fromRaw(hash, data) { + return new TXHash(hash).fromRaw(data); +}; + +/* + * Helpers + */ + +function cmp(a, b) { + return a - b; +} + +function cmpid(a, b) { + return a.id - b.id; +} + +/* + * Expose + */ + +exports = WalletBlock; +exports.WalletBlock = WalletBlock; +exports.TXHash = TXHash; + +module.exports = exports; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index e0043dcf..c177ad33 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -25,6 +25,7 @@ var ldb = require('../db/ldb'); var Bloom = require('../utils/bloom'); var Logger = require('../node/logger'); var TX = require('../primitives/tx'); +var WalletBlock = require('./walletblock'); var TXDB = require('./txdb'); var U32 = utils.U32; @@ -1804,279 +1805,10 @@ WalletDB.prototype._zap = co(function* zap(age) { } }); -/** - * Wallet Block - * @constructor - * @param {Hash} hash - * @param {Number} height - */ - -function WalletBlock(hash, height, ts) { - if (!(this instanceof WalletBlock)) - return new WalletBlock(hash, height, ts); - - this.hash = hash || constants.NULL_HASH; - this.height = height != null ? height : -1; - this.ts = ts || 0; - this.txs = []; - this.index = {}; -} - -/** - * Clone the block. - * @returns {WalletBlock} - */ - -WalletBlock.prototype.clone = function clone() { - return new WalletBlock(this.hash, this.height, this.ts); -}; - -/** - * Instantiate wallet block from chain entry. - * @private - * @param {ChainEntry} entry - */ - -WalletBlock.prototype.fromEntry = function fromEntry(entry) { - this.hash = entry.hash; - this.height = entry.height; - this.ts = entry.ts; - return this; -}; - -/** - * Instantiate wallet block from json object. - * @private - * @param {Object} json - */ - -WalletBlock.prototype.fromJSON = function fromJSON(json) { - this.hash = utils.revHex(json.hash); - this.height = json.height; - this.ts = json.ts; - return this; -}; - -/** - * Instantiate wallet block from serialized data. - * @private - * @param {Hash} hash - * @param {Buffer} data - */ - -WalletBlock.prototype.fromRaw = function fromRaw(data) { - var p = new BufferReader(data); - var i, hash, tx, count; - - this.hash = p.readHash('hex'); - this.height = p.readU32(); - this.ts = p.readU32(); - - while (p.left()) { - hash = p.readHash('hex'); - tx = new TXHash(hash); - count = p.readVarint(); - for (i = 0; i < count; i++) - tx.wids.push(p.readU32()); - this.txs.push(tx); - this.index[tx.hash] = tx; - } - - return this; -}; - -/** - * Instantiate wallet block from serialized tip data. - * @private - * @param {Buffer} data - */ - -WalletBlock.prototype.fromTip = function fromTip(data) { - var p = new BufferReader(data); - this.hash = p.readHash('hex'); - this.height = p.readU32(); - this.ts = p.readU32(); - return this; -}; - -/** - * Instantiate wallet block from chain entry. - * @param {ChainEntry} entry - * @returns {WalletBlock} - */ - -WalletBlock.fromEntry = function fromEntry(entry) { - return new WalletBlock().fromEntry(entry); -}; - -/** - * Instantiate wallet block from json object. - * @param {Object} json - * @returns {WalletBlock} - */ - -WalletBlock.fromJSON = function fromJSON(json) { - return new WalletBlock().fromJSON(json); -}; - -/** - * Instantiate wallet block from serialized data. - * @param {Hash} hash - * @param {Buffer} data - * @returns {WalletBlock} - */ - -WalletBlock.fromRaw = function fromRaw(data) { - return new WalletBlock().fromRaw(data); -}; - -/** - * Instantiate wallet block from serialized tip data. - * @private - * @param {Buffer} data - */ - -WalletBlock.fromTip = function fromTip(data) { - return new WalletBlock().fromTip(data); -}; - -/** - * Serialize the wallet block as a tip (hash and height). - * @returns {Buffer} - */ - -WalletBlock.prototype.toTip = function toTip(writer) { - var p = new BufferWriter(writer); - - p.writeHash(this.hash); - p.writeU32(this.height); - p.writeU32(this.ts); - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Serialize the wallet block as a block. - * Contains matching transaction hashes. - * @returns {Buffer} - */ - -WalletBlock.prototype.toRaw = function toRaw(writer) { - var p = new BufferWriter(writer); - var i, j, tx; - - p.writeHash(this.hash); - p.writeU32(this.height); - p.writeU32(this.ts); - - for (i = 0; i < this.txs.length; i++) { - tx = this.txs[i]; - p.writeHash(tx.hash); - p.writeVarint(tx.wids.length); - for (j = 0; j < tx.wids.length; j++) - p.writeU32(tx.wids[j]); - } - - if (!writer) - p = p.render(); - - return p; -}; - -/** - * Add a hash and wid pair to the block. - * @param {Hash} hash - * @param {WalletID} wid - * @returns {Boolean} - */ - -WalletBlock.prototype.add = function add(hash, wid) { - var tx = this.index[hash]; - - if (!tx) { - tx = new TXHash(hash); - tx.wids.push(wid); - this.txs.push(tx); - this.index[tx.hash] = tx; - return true; - } - - return tx.add(wid); -}; - -/** - * Remove a hash and wid pair from the block. - * @param {Hash} hash - * @param {WalletID} wid - * @returns {Boolean} - */ - -WalletBlock.prototype.remove = function remove(hash, wid) { - var tx = this.index[hash]; - var result; - - if (!tx) - return false; - - if (!tx.remove(wid)) - return false; - - if (tx.wids.length === 0) { - result = utils.binaryRemove(this.txs, tx, cmpid); - assert(result); - delete this.index[tx.hash]; - } - - return true; -}; - -/** - * Convert the block to a more json-friendly object. - * @returns {Object} - */ - -WalletBlock.prototype.toJSON = function toJSON() { - return { - hash: utils.revHex(this.hash), - height: this.height - }; -}; - /* * Helpers */ -function TXHash(hash, wids) { - this.hash = hash || constants.NULL_HASH; - this.wids = wids || []; - this.id = TXHash.id++; -} - -TXHash.id = 0; - -TXHash.prototype.add = function add(wid) { - return utils.binaryInsert(this.wids, wid, cmp, true) !== -1; -}; - -TXHash.prototype.remove = function remove(wid) { - return utils.binaryRemove(this.wids, wid, cmp); -}; - -TXHash.prototype.toRaw = function toRaw(writer) { - return serializeWallets(this.wids, writer); -}; - -TXHash.prototype.fromRaw = function fromRaw(data) { - return parseWallets(data); -}; - -TXHash.fromRaw = function fromRaw(hash, data) { - return new TXHash(hash).fromRaw(data); -}; - function parseWallets(data) { var p = new BufferReader(data); var wids = []; @@ -2106,10 +1838,6 @@ function cmp(a, b) { return a - b; } -function cmpid(a, b) { - return a.id - b.id; -} - /* * Expose */