From 9a3e3fba3a4c0512a358624f8209c86743f0e98f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 3 Oct 2016 19:07:01 -0700 Subject: [PATCH] wallet: make more state local to wallets. --- lib/wallet/account.js | 7 +- lib/wallet/wallet.js | 139 +++++++++-- lib/wallet/walletdb.js | 516 +++++++++++++---------------------------- 3 files changed, 290 insertions(+), 372 deletions(-) diff --git a/lib/wallet/account.js b/lib/wallet/account.js index 4db92269..30f6c098 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -48,6 +48,7 @@ function Account(db, options) { this.db = db; this.network = db.network; + this.wallet = null; this.lookahead = Account.MAX_LOOKAHEAD; this.receive = null; @@ -368,7 +369,7 @@ Account.prototype._checkKeys = co(function* _checkKeys() { ring = this.deriveReceive(0); hash = ring.getScriptHash('hex'); - return yield this.db.hasAddress(this.wid, hash); + return yield this.wallet.hasAddress(hash); }); /** @@ -587,7 +588,7 @@ Account.prototype.save = function save() { */ Account.prototype.saveKey = function saveKey(ring) { - return this.db.saveKey(this.wid, ring); + return this.db.saveKey(this.wallet, ring); }; /** @@ -597,7 +598,7 @@ Account.prototype.saveKey = function saveKey(ring) { */ Account.prototype.savePath = function savePath(path) { - return this.db.savePath(this.wid, path); + return this.db.savePath(this.wallet, path); }; /** diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 317d95d6..42c691c3 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -27,6 +27,7 @@ var Account = require('./account'); var MasterKey = require('./masterkey'); var Input = require('../primitives/input'); var Output = require('../primitives/output'); +var LRU = require('../utils/lru'); /** * BIP44 Wallet @@ -67,6 +68,10 @@ function Wallet(db, options) { this.readLock = new Locker.Mapped(); this.writeLock = new Locker(); this.fundLock = new Locker(); + this.indexCache = new LRU(10000); + this.accountCache = new LRU(10000); + this.pathCache = new LRU(100000); + this.batch = null; this.wid = 0; this.id = null; @@ -184,7 +189,7 @@ Wallet.prototype.init = co(function* init(options) { if (options.passphrase) yield this.master.encrypt(options.passphrase); - account = yield this.createAccount(options); + account = yield this._createAccount(options); assert(account); this.account = account; @@ -394,7 +399,7 @@ Wallet.prototype._encrypt = co(function* encrypt(passphrase) { try { key = yield this.master.encrypt(passphrase); - yield this.db.encryptKeys(this.wid, key); + yield this.db.encryptKeys(this, key); } catch (e) { this.drop(); throw e; @@ -440,7 +445,7 @@ Wallet.prototype._decrypt = co(function* decrypt(passphrase) { try { key = yield this.master.decrypt(passphrase); - yield this.db.decryptKeys(this.wid, key); + yield this.db.decryptKeys(this, key); } catch (e) { this.drop(); throw e; @@ -646,7 +651,7 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { var passphrase = options.passphrase; var timeout = options.timeout; var name = options.name; - var key, master, account; + var key, master, account, exists; if (typeof options.account === 'string') name = options.account; @@ -654,6 +659,11 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { if (!name) name = this.accountDepth + ''; + exists = yield this.hasAccount(name); + + if (exists) + throw new Error('Account already exists.'); + master = yield this.unlock(passphrase, timeout); if (this.watchOnly && options.accountKey) { @@ -686,12 +696,21 @@ Wallet.prototype._createAccount = co(function* createAccount(options) { this.start(); try { - account = yield this.db.createAccount(options); + account = Account.fromOptions(this.db, options); } catch (e) { this.drop(); throw e; } + account.wallet = this; + + yield account.init(); + + this.logger.info('Created account %s/%s/%d.', + account.id, + account.name, + account.accountIndex); + this.accountDepth++; this.save(); @@ -746,14 +765,14 @@ Wallet.prototype.getAddressHashes = function getAddressHashes() { */ Wallet.prototype.getAccount = co(function* getAccount(acct) { - var index, unlock; + var index, unlock, account; if (this.account) { if (acct === 0 || acct === 'default') return this.account; } - index = yield this.db.getAccountIndex(this.wid, acct); + index = yield this.getAccountIndex(acct); if (index === -1) return; @@ -773,28 +792,94 @@ Wallet.prototype.getAccount = co(function* getAccount(acct) { * @returns {Promise} - Returns {@link Account}. */ -Wallet.prototype._getAccount = co(function* _getAccount(index) { - var account = yield this.db.getAccount(this.wid, index); +Wallet.prototype._getAccount = co(function* getAccount(index) { + var account = this.accountCache.get(index); + + if (account) + return account; + + account = yield this.db.getAccount(this.wid, index); if (!account) return; + account.wallet = this; + + yield account.open(); + account.wid = this.wid; account.id = this.id; account.watchOnly = this.watchOnly; + this.accountCache.set(index, account); + return account; }); +/** + * Lookup the corresponding account name's index. + * @param {WalletID} wid + * @param {String|Number} name - Account name/index. + * @returns {Promise} - Returns Number. + */ + +Wallet.prototype.getAccountIndex = co(function* getAccountIndex(name) { + var key, index; + + if (name == null) + return -1; + + if (typeof name === 'number') + return name; + + index = this.indexCache.get(name); + + if (index != null) + return index; + + index = yield this.db.getAccountIndex(this.wid, name); + + if (index === -1) + return -1; + + this.indexCache.set(name, index); + + return index; +}); + +/** + * Lookup the corresponding account index's name. + * @param {WalletID} wid + * @param {Number} index - Account index. + * @returns {Promise} - Returns String. + */ + +Wallet.prototype.getAccountName = co(function* getAccountName(index) { + var account = yield this.getAccount(index); + + if (!account) + return null; + + return account.name; +}); + /** * Test whether an account exists. * @param {Number|String} acct * @returns {Promise} - Returns {@link Boolean}. */ -Wallet.prototype.hasAccount = function hasAccount(acct) { - return this.db.hasAccount(this.wid, acct); -}; +Wallet.prototype.hasAccount = co(function* hasAccount(acct) { + var index = yield this.getAccountIndex(acct); + + if (index === -1) + return false; + + if (this.accountCache.has(index)) + return true; + + return yield this.db.hasAccount(this.wid, index); +}); /** * Create a new receiving address (increments receiveDepth). @@ -896,7 +981,7 @@ Wallet.prototype.save = function save() { */ Wallet.prototype.start = function start() { - return this.db.start(this.wid); + return this.db.start(this); }; /** @@ -905,7 +990,7 @@ Wallet.prototype.start = function start() { */ Wallet.prototype.drop = function drop() { - return this.db.drop(this.wid); + return this.db.drop(this); }; /** @@ -914,7 +999,7 @@ Wallet.prototype.drop = function drop() { */ Wallet.prototype.commit = function commit() { - return this.db.commit(this.wid); + return this.db.commit(this); }; /** @@ -923,12 +1008,17 @@ Wallet.prototype.commit = function commit() { * @returns {Promise} - Returns Boolean. */ -Wallet.prototype.hasAddress = function hasAddress(address) { +Wallet.prototype.hasAddress = co(function* hasAddress(address) { var hash = Address.getHash(address, 'hex'); + var path; + if (!hash) - return Promise.resolve(false); - return this.db.hasAddress(this.wid, hash); -}; + return false; + + path = yield this.getPath(hash); + + return path != null; +}); /** * Get path by address hash. @@ -943,12 +1033,20 @@ Wallet.prototype.getPath = co(function* getPath(address) { if (!hash) return; + path = this.pathCache.get(hash); + + if (path) + return path; + path = yield this.db.getPath(this.wid, hash); if (!path) return; path.id = this.id; + path.name = yield this.getAccountName(path.account); + + this.pathCache.set(hash, path); return path; }); @@ -970,6 +1068,7 @@ Wallet.prototype.getPaths = co(function* getPaths(acct) { path = paths[i]; if (!account || path.account === account) { path.id = this.id; + path.name = yield this.getAccountName(path.account); out.push(path); } } @@ -1835,7 +1934,7 @@ Wallet.prototype._getIndex = co(function* _getIndex(acct) { if (acct == null) return null; - index = yield this.db.getAccountIndex(this.wid, acct); + index = yield this.getAccountIndex(acct); if (index === -1) throw new Error('Account not found.'); diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 2e5d527a..8c2298b6 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -157,9 +157,6 @@ function WalletDB(options) { this.txLock = new Locker(); this.widCache = new LRU(10000); - this.indexCache = new LRU(10000); - this.accountCache = new LRU(10000); - this.pathCache = new LRU(100000); this.pathMapCache = new LRU(100000); // Try to optimize for up to 1m addresses. @@ -293,13 +290,10 @@ WalletDB.prototype.getDepth = co(function* getDepth() { * @param {WalletID} wid */ -WalletDB.prototype.start = function start(wid) { - var batch; - assert(utils.isNumber(wid), 'Bad ID for batch.'); - assert(!this.batches[wid], 'Batch already started.'); - batch = this.db.batch(); - this.batches[wid] = batch; - return batch; +WalletDB.prototype.start = function start(wallet) { + assert(!wallet.batch, 'Batch already started.'); + wallet.batch = this.db.batch(); + return wallet.batch; }; /** @@ -308,10 +302,10 @@ WalletDB.prototype.start = function start(wid) { * @param {WalletID} wid */ -WalletDB.prototype.drop = function drop(wid) { - var batch = this.batch(wid); +WalletDB.prototype.drop = function drop(wallet) { + var batch = this.batch(wallet); + wallet.batch = null; batch.clear(); - delete this.batches[wid]; }; /** @@ -321,12 +315,9 @@ WalletDB.prototype.drop = function drop(wid) { * @returns {Leveldown.Batch} */ -WalletDB.prototype.batch = function batch(wid) { - var batch; - assert(utils.isNumber(wid), 'Bad ID for batch.'); - batch = this.batches[wid]; - assert(batch, 'Batch does not exist.'); - return batch; +WalletDB.prototype.batch = function batch(wallet) { + assert(wallet.batch, 'Batch does not exist.'); + return wallet.batch; }; /** @@ -336,9 +327,9 @@ WalletDB.prototype.batch = function batch(wid) { * @returns {Promise} */ -WalletDB.prototype.commit = function commit(wid) { - var batch = this.batch(wid); - delete this.batches[wid]; +WalletDB.prototype.commit = function commit(wallet) { + var batch = wallet.batch; + wallet.batch = null; return batch.write(); }; @@ -377,18 +368,11 @@ WalletDB.prototype.loadFilter = co(function* loadFilter() { * @returns {Boolean} */ -WalletDB.prototype.testFilter = function testFilter(hashes) { - var i; - +WalletDB.prototype.testFilter = function testFilter(hash) { if (!this.filter) return true; - for (i = 0; i < hashes.length; i++) { - if (this.filter.test(hashes[i], 'hex')) - return true; - } - - return false; + return this.filter.test(hash, 'hex'); }; /** @@ -511,7 +495,7 @@ WalletDB.prototype._get = co(function* get(wid) { WalletDB.prototype.save = function save(wallet) { var wid = wallet.wid; var id = wallet.id; - var batch = this.batch(wid); + var batch = this.batch(wallet); var buf = new Buffer(4); this.widCache.set(id, wid); @@ -557,7 +541,7 @@ WalletDB.prototype._rename = co(function* _rename(wallet, id) { this.widCache.remove(old); - paths = this.pathCache.values(); + paths = wallet.pathCache.values(); for (i = 0; i < paths.length; i++) { path = paths[i]; @@ -570,12 +554,12 @@ WalletDB.prototype._rename = co(function* _rename(wallet, id) { wallet.id = id; - batch = this.start(wallet.wid); + batch = this.start(wallet); batch.del(layout.l(old)); this.save(wallet); - yield this.commit(wallet.wid); + yield this.commit(wallet); }); /** @@ -586,8 +570,8 @@ WalletDB.prototype._rename = co(function* _rename(wallet, id) { */ WalletDB.prototype.renameAccount = co(function* renameAccount(account, name) { + var wallet = account.wallet; var old = account.name; - var key = account.wid + '/' + old; var i, paths, path, batch; assert(utils.isName(name), 'Bad account name.'); @@ -595,12 +579,12 @@ WalletDB.prototype.renameAccount = co(function* renameAccount(account, name) { if (account.accountIndex === 0) throw new Error('Cannot rename primary account.'); - if (yield this.hasAccount(account.wid, name)) + if (yield account.wallet.hasAccount(name)) throw new Error('Account name not available.'); - this.indexCache.remove(key); + wallet.indexCache.remove(old); - paths = this.pathCache.values(); + paths = wallet.pathCache.values(); for (i = 0; i < paths.length; i++) { path = paths[i]; @@ -616,12 +600,12 @@ WalletDB.prototype.renameAccount = co(function* renameAccount(account, name) { account.name = name; - batch = this.start(account.wid); + batch = this.start(wallet); batch.del(layout.i(account.wid, old)); this.saveAccount(account); - yield this.commit(account.wid); + yield this.commit(wallet); }); /** @@ -718,30 +702,6 @@ WalletDB.prototype.ensure = co(function* ensure(options) { return yield this.create(options); }); -/** - * Get an account from the database. - * @param {WalletID} wid - * @param {String|Number} acct - Account name/index. - * @returns {Promise} - Returns {@link Wallet}. - */ - -WalletDB.prototype.getAccount = co(function* getAccount(wid, acct) { - var index = yield this.getAccountIndex(wid, acct); - var account; - - if (index === -1) - return; - - account = yield this._getAccount(wid, index); - - if (!account) - return; - - yield account.open(); - - return account; -}); - /** * Get an account from the database by wid. * @private @@ -750,24 +710,14 @@ WalletDB.prototype.getAccount = co(function* getAccount(wid, acct) { * @returns {Promise} - Returns {@link Wallet}. */ -WalletDB.prototype._getAccount = co(function* getAccount(wid, index) { - var key = wid + '/' + index; - var account = this.accountCache.get(key); - var data; - - if (account) - return account; - - data = yield this.db.get(layout.a(wid, index)); +WalletDB.prototype.getAccount = co(function* getAccount(wid, index) { + var data = yield this.db.get(layout.a(wid, index)); + var account; if (!data) return; - account = Account.fromRaw(this, data); - - this.accountCache.set(key, account); - - return account; + return Account.fromRaw(this, data); }); /** @@ -811,49 +761,12 @@ WalletDB.prototype.getAccounts = co(function* getAccounts(wid) { */ WalletDB.prototype.getAccountIndex = co(function* getAccountIndex(wid, name) { - var key, index; - - if (!wid) - return -1; - - if (name == null) - return -1; - - if (typeof name === 'number') - return name; - - key = wid + '/' + name; - index = this.indexCache.get(key); - - if (index != null) - return index; - - index = yield this.db.get(layout.i(wid, name)); + var index = yield this.db.get(layout.i(wid, name)); if (!index) return -1; - index = index.readUInt32LE(0, true); - - this.indexCache.set(key, index); - - return index; -}); - -/** - * Lookup the corresponding account index's name. - * @param {WalletID} wid - * @param {Number} index - Account index. - * @returns {Promise} - Returns String. - */ - -WalletDB.prototype.getAccountName = co(function* getAccountName(wid, index) { - var account = yield this._getAccount(wid, index); - - if (!account) - return null; - - return account.name; + return index.readUInt32LE(0, true); }); /** @@ -864,10 +777,10 @@ WalletDB.prototype.getAccountName = co(function* getAccountName(wid, index) { WalletDB.prototype.saveAccount = function saveAccount(account) { var wid = account.wid; + var wallet = account.wallet; var index = account.accountIndex; var name = account.name; - var batch = this.batch(wid); - var key = wid + '/' + index; + var batch = this.batch(account.wallet); var buf = new Buffer(4); buf.writeUInt32LE(index, 0, true); @@ -875,34 +788,9 @@ WalletDB.prototype.saveAccount = function saveAccount(account) { batch.put(layout.a(wid, index), account.toRaw()); batch.put(layout.i(wid, name), buf); - this.accountCache.set(key, account); + wallet.accountCache.set(index, account); }; -/** - * Create an account. - * @param {Object} options - See {@link Account} options. - * @returns {Promise} - Returns {@link Account}. - */ - -WalletDB.prototype.createAccount = co(function* createAccount(options) { - var exists = yield this.hasAccount(options.wid, options.name); - var account; - - if (exists) - throw new Error('Account already exists.'); - - account = Account.fromOptions(this, options); - - yield account.init(); - - this.logger.info('Created account %s/%s/%d.', - account.id, - account.name, - account.accountIndex); - - return account; -}); - /** * Test for the existence of an account. * @param {WalletID} wid @@ -910,22 +798,7 @@ WalletDB.prototype.createAccount = co(function* createAccount(options) { * @returns {Promise} - Returns Boolean. */ -WalletDB.prototype.hasAccount = co(function* hasAccount(wid, acct) { - var index, key; - - if (!wid) - return false; - - index = yield this.getAccountIndex(wid, acct); - - if (index === -1) - return false; - - key = wid + '/' + index; - - if (this.accountCache.has(key)) - return true; - +WalletDB.prototype.hasAccount = co(function* hasAccount(wid, index) { return yield this.db.has(layout.a(wid, index)); }); @@ -962,8 +835,8 @@ WalletDB.prototype.getWalletsByHash = co(function* getWalletsByHash(hash) { * @returns {Promise} */ -WalletDB.prototype.saveKey = function saveKey(wid, ring) { - return this.savePath(wid, ring.toPath()); +WalletDB.prototype.saveKey = function saveKey(wallet, ring) { + return this.savePath(wallet, ring.toPath()); }; /** @@ -978,10 +851,10 @@ WalletDB.prototype.saveKey = function saveKey(wid, ring) { * @returns {Promise} */ -WalletDB.prototype.savePath = co(function* savePath(wid, path) { +WalletDB.prototype.savePath = co(function* savePath(wallet, path) { + var wid = wallet.wid; var hash = path.hash; - var batch = this.batch(wid); - var key = wid + hash; + var batch = this.batch(wallet); var wallets; if (this.filter) @@ -1000,7 +873,7 @@ WalletDB.prototype.savePath = co(function* savePath(wid, path) { wallets.push(wid); this.pathMapCache.set(hash, wallets); - this.pathCache.set(key, path); + wallet.pathCache.set(hash, path); batch.put(layout.p(hash), serializeWallets(wallets)); batch.put(layout.P(wid, hash), path.toRaw()); @@ -1014,7 +887,7 @@ WalletDB.prototype.savePath = co(function* savePath(wid, path) { WalletDB.prototype.getPaths = co(function* getPaths(hash) { var wallets = yield this.getWalletsByHash(hash); - var i, wid, path, paths; + var i, wid, path, paths, wallet; if (!wallets) return; @@ -1023,7 +896,13 @@ WalletDB.prototype.getPaths = co(function* getPaths(hash) { for (i = 0; i < wallets.length; i++) { wid = wallets[i]; - path = yield this.getPath(wid, hash); + wallet = yield this.get(wid); + + if (!wallet) + continue; + + path = yield wallet.getPath(hash); + if (path) paths.push(path); } @@ -1039,18 +918,7 @@ WalletDB.prototype.getPaths = co(function* getPaths(hash) { */ WalletDB.prototype.getPath = co(function* getPath(wid, hash) { - var key, path, data; - - if (!hash) - return; - - key = wid + hash; - path = this.pathCache.get(key); - - if (path) - return path; - - data = yield this.db.get(layout.P(wid, hash)); + var data = yield this.db.get(layout.P(wid, hash)); if (!data) return; @@ -1058,26 +926,10 @@ WalletDB.prototype.getPath = co(function* getPath(wid, hash) { path = Path.fromRaw(data); path.wid = wid; path.hash = hash; - path.name = yield this.getAccountName(wid, path.account); - - this.pathCache.set(key, path); return path; }); -/** - * Test whether an address hash exists in the - * path map and is relevant to the wallet id. - * @param {WalletID} wid - * @param {Hash} hash - * @returns {Promise} - */ - -WalletDB.prototype.hasAddress = co(function* hasAddress(wid, hash) { - var path = yield this.getPath(wid, hash); - return path != null; -}); - /** * Get all address hashes. * @returns {Promise} @@ -1126,7 +978,6 @@ WalletDB.prototype.getWalletPaths = co(function* getWalletPaths(wid) { path.hash = hash; path.wid = wid; - path.name = yield this.getAccountName(wid, path.account); items[i] = path; } @@ -1153,9 +1004,10 @@ WalletDB.prototype.getWallets = function getWallets() { * @returns {Promise} */ -WalletDB.prototype.encryptKeys = co(function* encryptKeys(wid, key) { - var paths = yield this.getWalletPaths(wid); - var batch = this.batch(wid); +WalletDB.prototype.encryptKeys = co(function* encryptKeys(wallet, key) { + var wid = wallet.wid; + var paths = yield wallet.getPaths(); + var batch = this.batch(wallet); var i, path, iv; for (i = 0; i < paths.length; i++) { @@ -1166,7 +1018,7 @@ WalletDB.prototype.encryptKeys = co(function* encryptKeys(wid, key) { iv = iv.slice(0, 16); path.data = crypto.encipher(path.data, key, iv); path.encrypted = true; - this.pathCache.set(wid + path.hash, path); + wallet.pathCache.set(path.hash, path); batch.put(layout.P(wid, path.hash), path.toRaw()); } } @@ -1178,9 +1030,10 @@ WalletDB.prototype.encryptKeys = co(function* encryptKeys(wid, key) { * @returns {Promise} */ -WalletDB.prototype.decryptKeys = co(function* decryptKeys(wid, key) { - var paths = yield this.getWalletPaths(wid); - var batch = this.batch(wid); +WalletDB.prototype.decryptKeys = co(function* decryptKeys(wallet, key) { + var wid = wallet.wid; + var paths = yield wallet.getPaths(); + var batch = this.batch(wallet); var i, path, iv; for (i = 0; i < paths.length; i++) { @@ -1191,7 +1044,7 @@ WalletDB.prototype.decryptKeys = co(function* decryptKeys(wid, key) { iv = iv.slice(0, 16); path.data = crypto.decipher(path.data, key, iv); path.encrypted = false; - this.pathCache.set(wid + path.hash, path); + wallet.pathCache.set(path.hash, path); batch.put(layout.P(wid, path.hash), path.toRaw()); } } @@ -1317,17 +1170,67 @@ WalletDB.prototype.resend = co(function* resend() { WalletDB.prototype.mapWallets = co(function* mapWallets(tx) { var hashes = tx.getHashes('hex'); - var table; + var wallets = yield this.getWalletsByHashes(hashes); + var info = []; + var i, wallets, item; - if (!this.testFilter(hashes)) + if (wallets.length === 0) return; - table = yield this.getTable(hashes); + for (i = 0; i < wallets.length; i++) { + item = wallets[i]; + info.push(new PathInfo(item.wallet, tx, item.matches)); + } - if (!table) - return; + return info; +}); - return PathInfo.map(this, tx, table); +/** + * Get all wallets by multiple address hashes. + * @param {Hash[]} hashes + * @returns {Promise} + */ + +WalletDB.prototype.getWalletsByHashes = co(function* getWalletsByHashes(hashes) { + var map = {}; + var result = []; + var i, j, hash, wids, wid, wallet, item, path; + + for (i = 0; i < hashes.length; i++) { + hash = hashes[i]; + + if (!this.testFilter(hash)) + continue; + + wids = yield this.getWalletsByHash(hash); + + for (j = 0; j < wids.length; j++) { + wid = wids[j]; + item = map[wid]; + + if (item) { + wallet = item.wallet; + path = yield wallet.getPath(hash); + assert(path); + item.matches.push(path); + continue; + } + + wallet = yield this.get(wid); + assert(wallet); + + path = yield wallet.getPath(hash); + assert(path); + + item = new WalletMatch(wallet); + item.matches.push(path); + + map[wid] = item; + result.push(item); + } + } + + return result; }); /** @@ -1338,46 +1241,22 @@ WalletDB.prototype.mapWallets = co(function* mapWallets(tx) { WalletDB.prototype.getPathInfo = co(function* getPathInfo(wallet, tx) { var hashes = tx.getHashes('hex'); - var table = yield this.getTable(hashes); - var info; - - if (!table) - return; - - info = new PathInfo(this, wallet.wid, tx, table); - info.id = wallet.id; - - return info; -}); - -/** - * Map address hashes to paths. - * @param {Hash[]} hashes - Address hashes. - * @returns {Promise} - Returns {@link AddressTable}. - */ - -WalletDB.prototype.getTable = co(function* getTable(hashes) { - var table = {}; - var match = false; - var i, hash, paths; + var paths = []; + var i, hash, path; for (i = 0; i < hashes.length; i++) { hash = hashes[i]; - paths = yield this.getPaths(hash); - if (!paths) { - table[hash] = []; + if (!this.testFilter(hash)) continue; - } - table[hash] = paths; - match = true; + path = yield wallet.getPath(hash); + + if (path) + paths.push(path); } - if (!match) - return; - - return table; + return new PathInfo(wallet, tx, paths); }); /** @@ -1672,14 +1551,12 @@ WalletDB.prototype._addTX = co(function* addTX(tx, force) { for (i = 0; i < wallets.length; i++) { info = wallets[i]; - wallet = yield this.get(info.wid); + wallet = info.wallet; if (!wallet) continue; - this.logger.debug('Adding tx to wallet: %s', wallet.id); - - info.id = wallet.id; + this.logger.debug('Adding tx to wallet: %s', info.id); yield wallet.txdb.add(tx, info); yield wallet.handleTX(info); @@ -1697,12 +1574,9 @@ WalletDB.prototype._addTX = co(function* addTX(tx, force) { * @param {Object} table */ -function PathInfo(db, wid, tx, table) { +function PathInfo(wallet, tx, paths) { if (!(this instanceof PathInfo)) - return new PathInfo(db, wid, tx, table); - - // Reference to the walletdb. - this.db = db; + return new PathInfo(wallet, tx, paths); // All relevant Accounts for // inputs and outputs (for database indexing). @@ -1711,16 +1585,16 @@ function PathInfo(db, wid, tx, table) { // All output paths (for deriving during sync). this.paths = []; + // Wallet + this.wallet = wallet; + // Wallet ID - this.wid = wid; + this.wid = wallet.wid; - // Wallet Label (passed in by caller). - this.id = null; + // Wallet Label + this.id = wallet.id; - // Map of address hashes->paths (for everything). - this.table = null; - - // Map of address hashes->paths (specific to wallet). + // Map of address hashes->paths. this.pathMap = {}; // Current transaction. @@ -1731,47 +1605,9 @@ function PathInfo(db, wid, tx, table) { this._json = null; if (tx) - this.fromTX(tx, table); + this.fromTX(tx, paths); } -/** - * Map a transaction to multiple wallets. - * @param {WalletDB} db - * @param {TX} tx - * @param {Object} table - * @returns {PathInfo[]} - */ - -PathInfo.map = function map(db, tx, table) { - var hashes = Object.keys(table); - var wallets = []; - var info = []; - var uniq = {}; - var i, j, hash, paths, path, wid; - - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - paths = table[hash]; - for (j = 0; j < paths.length; j++) { - path = paths[j]; - if (!uniq[path.wid]) { - uniq[path.wid] = true; - wallets.push(path.wid); - } - } - } - - if (wallets.length === 0) - return; - - for (i = 0; i < wallets.length; i++) { - wid = wallets[i]; - info.push(new PathInfo(db, wid, tx, table)); - } - - return info; -}; - /** * Instantiate path info from a transaction. * @private @@ -1780,31 +1616,20 @@ PathInfo.map = function map(db, tx, table) { * @returns {PathInfo} */ -PathInfo.prototype.fromTX = function fromTX(tx, table) { +PathInfo.prototype.fromTX = function fromTX(tx, paths) { var uniq = {}; var i, j, hashes, hash, paths, path; this.tx = tx; - this.table = table; - hashes = Object.keys(table); + for (i = 0; i < paths.length; i++) { + path = paths[i]; - for (i = 0; i < hashes.length; i++) { - hash = hashes[i]; - paths = table[hash]; + this.pathMap[path.hash] = path; - for (j = 0; j < paths.length; j++) { - path = paths[j]; - - if (path.wid !== this.wid) - continue; - - this.pathMap[hash] = path; - - if (!uniq[path.account]) { - uniq[path.account] = true; - this.accounts.push(path.account); - } + if (!uniq[path.account]) { + uniq[path.account] = true; + this.accounts.push(path.account); } } @@ -1812,16 +1637,8 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) { for (i = 0; i < hashes.length; i++) { hash = hashes[i]; - paths = table[hash]; - - for (j = 0; j < paths.length; j++) { - path = paths[j]; - - if (path.wid !== this.wid) - continue; - - this.paths.push(path); - } + paths = this.pathMap[hash]; + this.paths.push(path); } return this; @@ -1836,8 +1653,8 @@ PathInfo.prototype.fromTX = function fromTX(tx, table) { * @returns {PathInfo} */ -PathInfo.fromTX = function fromTX(db, wid, tx, table) { - return new PathInfo(db, wid).fromTX(tx, table); +PathInfo.fromTX = function fromTX(wallet, tx, paths) { + return new PathInfo(wallet).fromTX(tx, paths); }; /** @@ -1909,8 +1726,8 @@ function Details(info) { if (!(this instanceof Details)) return new Details(info); - this.db = info.db; - this.network = info.db.network; + this.db = info.wallet.db; + this.network = this.db.network; this.wid = info.wid; this.id = info.id; this.hash = info.tx.hash('hex'); @@ -1925,7 +1742,7 @@ function Details(info) { this.inputs = []; this.outputs = []; - this.init(info.table); + this.init(info.pathMap); } /** @@ -1935,9 +1752,9 @@ function Details(info) { * @param {Object} table */ -Details.prototype.init = function init(table) { - this._insert(this.tx.inputs, this.inputs, table); - this._insert(this.tx.outputs, this.outputs, table); +Details.prototype.init = function init(map) { + this._insert(this.tx.inputs, true, this.inputs, map); + this._insert(this.tx.outputs, false, this.outputs, map); }; /** @@ -1948,17 +1765,19 @@ Details.prototype.init = function init(table) { * @param {Object} table */ -Details.prototype._insert = function _insert(vector, target, table) { +Details.prototype._insert = function _insert(vector, input, target, map) { var i, j, io, address, hash, paths, path, member; for (i = 0; i < vector.length; i++) { io = vector[i]; member = new DetailsMember(); - if (io.prevout) - member.value = io.coin ? io.coin.value : 0; - else + if (input) { + if (io.coin) + member.value = io.coin.value; + } else { member.value = io.value; + } address = io.getAddress(); @@ -1966,16 +1785,10 @@ Details.prototype._insert = function _insert(vector, target, table) { member.address = address; hash = address.getHash('hex'); - paths = table[hash]; + path = map[hash]; - for (j = 0; j < paths.length; j++) { - path = paths[j]; - if (path.wid === this.wid) { - path.id = this.id; - member.path = path; - break; - } - } + if (path) + member.path = path; } target.push(member); @@ -2237,6 +2050,11 @@ function serializeInfo(wallets) { return p.render(); } +function WalletMatch(wallet) { + this.wallet = wallet; + this.matches = []; +} + /* * Expose */