diff --git a/lib/bcoin/keyring.js b/lib/bcoin/keyring.js index a2acc1b5..8f5fa370 100644 --- a/lib/bcoin/keyring.js +++ b/lib/bcoin/keyring.js @@ -38,6 +38,7 @@ function KeyRing(options) { this.m = options.m || 1; this.n = options.n || 1; this.witness = options.witness || false; + this.name = options.name; this.account = options.account; this.change = options.change; this.index = options.index; @@ -527,6 +528,7 @@ KeyRing.prototype.toJSON = function toJSON() { m: this.m, n: this.n, witness: this.witness, + name: this.name, account: this.account, change: this.change, index: this.index, @@ -552,6 +554,7 @@ KeyRing.fromJSON = function fromJSON(json) { m: json.m, n: json.n, witness: json.witness, + name: json.name, account: json.account, change: json.change, index: json.index, @@ -574,6 +577,7 @@ KeyRing.prototype.toRaw = function toRaw(writer) { p.writeU8(this.m); p.writeU8(this.n); p.writeU8(this.witness ? 1 : 0); + p.writeVarString(this.name, 'utf8'); p.writeU32(this.account); p.writeU32(this.change); p.writeU32(this.index); @@ -601,6 +605,7 @@ KeyRing.fromRaw = function fromRaw(data) { var m = p.readU8(); var n = p.readU8(); var witness = p.readU8() === 1; + var name = p.readVarString('utf8'); var account = p.readU32(); var change = p.readU32(); var index = p.readU32(); @@ -617,6 +622,7 @@ KeyRing.fromRaw = function fromRaw(data) { m: m, n: n, witness: witness, + name: name, account: account, change: change, index: index, diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index a6df050a..9dcf664d 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -126,6 +126,25 @@ TXDB.prototype._testFilter = function _testFilter(addresses) { * @param {Function} callback - Returns [Error, {@link AddressMap}]. */ +function uniq(obj) { + var uniq = {}; + var out = []; + var i, key, value; + + for (i = 0; i < obj.length; i++) { + value = obj[i]; + key = value.id + '/' + value.account; + + if (uniq[key]) + continue; + + uniq[key] = true; + out.push(value); + } + + return out; +} + TXDB.prototype.getMap = function getMap(tx, callback) { var input, output, addresses, table, map; @@ -160,9 +179,9 @@ TXDB.prototype.getMap = function getMap(tx, callback) { map.output = map.output.concat(map.table[address]); }); - map.input = utils.uniq(map.input); - map.output = utils.uniq(map.output); - map.all = utils.uniq(map.input.concat(map.output)); + map.input = uniq(map.input); + map.output = uniq(map.output); + map.all = uniq(map.input.concat(map.output)); return callback(null, map); } @@ -179,6 +198,8 @@ TXDB.prototype.getMap = function getMap(tx, callback) { TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { var self = this; var table = { count: 0 }; + var values = []; + var i, keys; if (Array.isArray(address)) { return utils.forEachSerial(address, function(address, next) { @@ -204,8 +225,18 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { if (err) return callback(err); - table[address] = paths ? Object.keys(paths) : []; - table.count += table[address].length; + if (!paths) { + table[address] = []; + return callback(null, table); + } + + keys = Object.keys(paths); + + for (i = 0; i < keys.length; i++) + values.push(paths[keys[i]]); + + table[address] = values; + table.count += values.length; return callback(null, table); }); @@ -357,7 +388,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) { batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY); } - map.all.forEach(function(id) { + map.all.forEach(function(path) { + var id = path.id + '/' + path.account; batch.put('T/' + id + '/' + hash, DUMMY); if (tx.ts === 0) { batch.put('P/' + id + '/' + hash, DUMMY); @@ -400,7 +432,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) { updated = true; if (address) { - map.table[address].forEach(function(id) { + map.table[address].forEach(function(path) { + var id = path.id + '/' + path.account; batch.del('C/' + id + '/' + key); }); } @@ -537,7 +570,8 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (!orphans) { if (address) { - map.table[address].forEach(function(id) { + map.table[address].forEach(function(path) { + var id = path.id + '/' + path.account; batch.put('C/' + id + '/' + key, DUMMY); }); } @@ -712,7 +746,8 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { batch.del('m/' + pad32(existing.ps) + '/' + hash); batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY); - map.all.forEach(function(id) { + map.all.forEach(function(path) { + var id = path.id + '/' + path.account; batch.del('P/' + id + '/' + hash); batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash); @@ -859,7 +894,8 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { batch.del('m/' + pad32(tx.ts) + '/' + hash); } - map.all.forEach(function(id) { + map.all.forEach(function(path) { + var id = path.id + '/' + path.account; batch.del('T/' + id + '/' + hash); if (tx.ts === 0) { batch.del('P/' + id + '/' + hash); @@ -888,7 +924,8 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { return; if (address) { - map.table[address].forEach(function(id) { + map.table[address].forEach(function(path) { + var id = path.id + '/' + path.account; batch.put('C/' + id + '/' + key, DUMMY); }); } @@ -909,7 +946,8 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { return; if (address) { - map.table[address].forEach(function(id) { + map.table[address].forEach(function(path) { + var id = path.id + '/' + path.account; batch.del('C/' + id + '/' + key); }); } @@ -1007,7 +1045,8 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { batch.del('m/' + pad32(ts) + '/' + hash); batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); - map.all.forEach(function(id) { + map.all.forEach(function(path) { + var id = path.id + '/' + path.account; batch.put('P/' + id + '/' + hash, DUMMY); batch.del('H/' + id + '/' + pad32(height) + '/' + hash); batch.del('M/' + id + '/' + pad32(ts) + '/' + hash); @@ -1081,12 +1120,12 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { } this.db.iterate({ - gte: address ? 'T/' + address : 't', - lte: address ? 'T/' + address + '~' : 't~', + gte: address ? 'T/' + address + '/' : 't', + lte: address ? 'T/' + address + '/~' : 't~', transform: function(key) { key = key.split('/'); if (address) - return key[2]; + return key[3]; return key[1]; } }, callback); @@ -1131,12 +1170,12 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal } this.db.iterate({ - gte: address ? 'P/' + address : 'p', - lte: address ? 'P/' + address + '~' : 'p~', + gte: address ? 'P/' + address + '/' : 'p', + lte: address ? 'P/' + address + '/~' : 'p~', transform: function(key) { key = key.split('/'); if (address) - return key[2]; + return key[3]; return key[1]; } }, callback); @@ -1178,12 +1217,12 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { } this.db.iterate({ - gte: address ? 'C/' + address : 'c', - lte: address ? 'C/' + address + '~' : 'c~', + gte: address ? 'C/' + address + '/' : 'c', + lte: address ? 'C/' + address + '/~' : 'c~', transform: function(key) { key = key.split('/'); if (address) - return [key[2], +key[3]]; + return [key[3], +key[4]]; return [key[1], +key[2]]; } }, callback); @@ -1221,7 +1260,7 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, opt transform: function(key) { key = key.split('/'); if (address) - return key[3]; + return key[4]; return key[2]; } }, callback); @@ -1268,7 +1307,7 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callba transform: function(key) { key = key.split('/'); if (address) - return key[3]; + return key[4]; return key[2]; } }, callback); diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 3a8555b7..94e25daa 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -544,6 +544,7 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) { change: change, index: index, type: this.type, + name: 'default', witness: this.witness, m: this.m, n: this.n, diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 387c5198..37d769d9 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -121,22 +121,22 @@ WalletDB.prototype._init = function _init() { this.tx.on('tx', function(tx, map) { self.emit('tx', tx, map); - map.all.forEach(function(id) { - self.fire(id, 'tx', tx); + map.all.forEach(function(path) { + self.fire(path.id, 'tx', tx); }); }); this.tx.on('confirmed', function(tx, map) { self.emit('confirmed', tx, map); - map.all.forEach(function(id) { - self.fire(id, 'confirmed', tx); + map.all.forEach(function(path) { + self.fire(path.id, 'confirmed', tx); }); }); this.tx.on('unconfirmed', function(tx, map) { self.emit('unconfirmed', tx, map); - map.all.forEach(function(id) { - self.fire(id, 'unconfirmed', tx); + map.all.forEach(function(path) { + self.fire(path.id, 'unconfirmed', tx); }); }); @@ -144,24 +144,24 @@ WalletDB.prototype._init = function _init() { var balances = {}; self.emit('updated', tx, map); - map.all.forEach(function(id) { - self.fire(id, 'updated', tx); + map.all.forEach(function(path) { + self.fire(path.id, 'updated', tx); }); - utils.forEachSerial(map.output, function(id, next) { + utils.forEachSerial(map.output, function(path, next) { if (self.listeners('balance').length === 0 - && !self.hasListener(id, 'balance')) { + && !self.hasListener(path.id, 'balance')) { return next(); } - self.getBalance(id, function(err, balance) { + self.getBalance(path.id, function(err, balance) { if (err) return next(err); - balances[id] = balance; + balances[path.id] = balance; - self.emit('balance', balance, id); - self.fire(id, 'balance', balance); + self.emit('balance', balance, path.id); + self.fire(path.id, 'balance', balance); next(); }); @@ -189,8 +189,8 @@ WalletDB.prototype._init = function _init() { WalletDB.prototype.sync = function sync(tx, map, callback) { var self = this; - utils.forEachSerial(map.output, function(id, next) { - self.syncOutputDepth(id, tx, next); + utils.forEachSerial(map.output, function(path, next) { + self.syncOutputDepth(path.id, tx, next); }, callback); }; @@ -296,7 +296,7 @@ WalletDB.prototype.hasListener = function hasListener(id, event) { }; /** - * Get a wallet from the database, instantiate, decrypt, and setup watcher. + * Get a wallet from the database, setup watcher. * @param {WalletID} id * @param {Function} callback - Returns [Error, {@link Wallet}]. */ @@ -314,12 +314,9 @@ WalletDB.prototype.get = function get(id, callback) { } this.db.get('w/' + id, function(err, data) { - if (err && err.type === 'NotFoundError') + if (err && err.type !== 'NotFoundError') return callback(); - if (err) - return callback(err); - if (!data) return callback(); @@ -350,6 +347,71 @@ WalletDB.prototype.save = function save(wallet, callback) { this.db.put('w/' + wallet.id, wallet.toRaw(), callback); }; +/** + * Get an account from the database. + * @param {WalletID} id + * @param {Function} callback - Returns [Error, {@link Wallet}]. + */ + +WalletDB.prototype.getAccountIndex = function getAccountIndex(wid, name, callback) { + return this.db.get('a/' + wid + '/' + name, function(err, index) { + if (err && err.type !== 'NotFoundError') + return callback(); + + if (!index) + return callback(null, -1); + + return callback(null, index.readUInt32LE(0, true)); + }); +}; + +WalletDB.prototype.getAccount = function getAccount(wid, id, callback) { + var self = this; + var aid = wid + '/' + id; + var account; + + if (!id) + return callback(); + + if (typeof id === 'string') { + return this.getAccountIndex(wid, id, function(err, index) { + if (err) + return callback(err); + + if (index === -1) + return callback(); + + return self.getAccount(wid, index, callback); + }); + } + + this.db.get('a/' + aid, function(err, data) { + if (err && err.type !== 'NotFoundError') + return callback(); + + if (err) + return callback(err); + + if (!data) + return callback(); + + try { + data = bcoin.account.parseRaw(data); + data.db = self; + account = bcoin.account.fromRaw(data); + } catch (e) { + return callback(e); + } + + account.open(function(err) { + if (err) + return callback(err); + + return callback(null, account); + }); + }); +}; + /** * Remove wallet from the database. Destroy wallet if passed in. * @param {WalletID} id @@ -365,6 +427,24 @@ WalletDB.prototype.remove = function remove(id, callback) { }); }; +/** + * Save a wallet to the database. + * @param {Wallet} wallet + * @param {Function} callback + */ + +WalletDB.prototype.saveAccount = function saveAccount(account, callback) { + var index = new Buffer(4); + this.db.put('a/' + account.wid + '/' + account.index, account.toRaw(), function(err) { + if (err) + return callback(err); + + index.writeUInt32LE(account.index, 0, true); + + self.db.put('a/' + account.wid + '/' + account.name, index, callback); + }); +}; + /** * Create a new wallet, save to database, setup watcher. * @param {Object} options - See {@link Wallet}. @@ -814,6 +894,8 @@ function parsePaths(data) { while (p.left()) { id = p.readVarString('utf8'); out[id] = { + id: id, + name: p.readVarString('utf8'), account: p.readU32(), change: p.readU32(), index: p.readU32() @@ -832,6 +914,7 @@ function serializePaths(out) { id = keys[i]; path = out[id]; p.writeVarString(id, 'utf8'); + p.writeVarString(path.name, 'utf8'); p.writeU32(path.account); p.writeU32(path.change); p.writeU32(path.index);