From 5b448b5bce9d820b1bbc9fe3fdb218027bb5542e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 30 Dec 2017 03:37:55 -0800 Subject: [PATCH] wallet: add wid->id index. --- lib/types.js | 6 -- lib/wallet/account.js | 9 +- lib/wallet/layout.js | 2 + lib/wallet/wallet.js | 13 +-- lib/wallet/walletdb.js | 199 ++++++++++++++++++++++++++-------------- migrate/walletdb6to7.js | 13 ++- 6 files changed, 143 insertions(+), 99 deletions(-) diff --git a/lib/types.js b/lib/types.js index fac6ea31..6787ce4a 100644 --- a/lib/types.js +++ b/lib/types.js @@ -48,12 +48,6 @@ * @global */ -/** - * Wallet ID - * @typedef {String} WalletID - * @global - */ - /** * Base58 string. * @typedef {String} Base58String diff --git a/lib/wallet/account.js b/lib/wallet/account.js index ae72b7df..7f4a7fe4 100644 --- a/lib/wallet/account.js +++ b/lib/wallet/account.js @@ -842,8 +842,7 @@ class Account { getSize() { let size = 0; - size += encoding.sizeVarString(this.name, 'ascii'); - size += 96; + size += 92; size += this.keys.length * 74; return size; } @@ -865,8 +864,6 @@ class Account { if (this.witness) flags |= 2; - bw.writeU32(this.accountIndex); - bw.writeVarString(this.name, 'ascii'); bw.writeU8(flags); bw.writeU8(this.type); bw.writeU8(this.m); @@ -893,10 +890,6 @@ class Account { fromRaw(data) { const br = bio.read(data); - - this.accountIndex = br.readU32(); - this.name = br.readVarString('ascii'); - const flags = br.readU8(); this.initialized = (flags & 1) !== 0; diff --git a/lib/wallet/layout.js b/lib/wallet/layout.js index 17875616..dfaebf5d 100644 --- a/lib/wallet/layout.js +++ b/lib/wallet/layout.js @@ -18,6 +18,7 @@ const bdb = require('bdb'); * P[wid][addr-hash] -> path data * r[wid][index][hash] -> path account index * w[wid] -> wallet + * W[wid] -> wallet id * l[id] -> wid * a[wid][index] -> account * i[wid][name] -> account index @@ -38,6 +39,7 @@ exports.wdb = { P: bdb.key('P', ['uint32', 'hash']), r: bdb.key('r', ['uint32', 'uint32', 'hash']), w: bdb.key('w', ['uint32']), + W: bdb.key('W', ['uint32']), l: bdb.key('l', ['ascii']), a: bdb.key('a', ['uint32', 'uint32']), i: bdb.key('i', ['uint32', 'ascii']), diff --git a/lib/wallet/wallet.js b/lib/wallet/wallet.js index 0325f9b3..dc1fc7fd 100644 --- a/lib/wallet/wallet.js +++ b/lib/wallet/wallet.js @@ -158,9 +158,7 @@ class Wallet extends EventEmitter { * @returns {Promise} */ - async init(options) { - const passphrase = options.passphrase; - + async init(options, passphrase) { if (passphrase) await this.master.encrypt(passphrase); @@ -741,7 +739,6 @@ class Wallet extends EventEmitter { /** * Lookup the corresponding account index's name. - * @param {WalletID} wid * @param {Number} index - Account index. * @returns {Promise} - Returns String. */ @@ -2269,8 +2266,7 @@ class Wallet extends EventEmitter { getSize() { let size = 0; - size += 45; - size += encoding.sizeVarString(this.id, 'ascii'); + size += 41; size += this.master.getSize(); return size; } @@ -2289,8 +2285,6 @@ class Wallet extends EventEmitter { if (this.watchOnly) flags |= 1; - bw.writeU32(this.wid); - bw.writeVarString(this.id, 'ascii'); bw.writeU8(flags); bw.writeU32(this.accountDepth); bw.writeBytes(this.token); @@ -2309,9 +2303,6 @@ class Wallet extends EventEmitter { fromRaw(data) { const br = bio.read(data); - this.wid = br.readU32(); - this.id = br.readVarString('ascii'); - const flags = br.readU8(); this.watchOnly = (flags & 1) !== 0; diff --git a/lib/wallet/walletdb.js b/lib/wallet/walletdb.js index 18ecf88a..4f29a09d 100644 --- a/lib/wallet/walletdb.js +++ b/lib/wallet/walletdb.js @@ -607,7 +607,7 @@ class WalletDB extends EventEmitter { const raw = await this.db.get(layout.D.build()); if (!raw) - return 1; + return 0; return raw.readUInt32LE(0, true); } @@ -676,39 +676,64 @@ class WalletDB extends EventEmitter { this.wallets.delete(wallet.wid); } + /** + * Map wallet id to wid. + * @param {String|Number} id + * @returns {Promise} - Returns {Number}. + */ + + async ensureWID(id) { + if (typeof id === 'number') { + if (!await this.db.has(layout.W.build(id))) + return -1; + return id; + } + + return this.getWID(id); + } + /** * Map wallet id to wid. * @param {String} id - * @returns {Promise} - Returns {WalletID}. + * @returns {Promise} - Returns {Number}. */ async getWID(id) { - if (!id) - return null; - - if (typeof id === 'number') - return id; - const data = await this.db.get(layout.l.build(id)); if (!data) - return null; + return -1; assert(data.length === 4); return data.readUInt32LE(0, true); } + /** + * Map wallet wid to id. + * @param {Number} wid + * @returns {Promise} - Returns {String}. + */ + + async getID(wid) { + const data = await this.db.get(layout.W.build(wid)); + + if (!data) + return null; + + return toString(data); + } + /** * Get a wallet from the database, setup watcher. - * @param {WalletID} wid + * @param {Number|String} id * @returns {Promise} - Returns {@link Wallet}. */ async get(id) { - const wid = await this.getWID(id); + const wid = await this.ensureWID(id); - if (!wid) + if (wid === -1) return null; const unlock = await this.readLock.lock(wid); @@ -723,7 +748,7 @@ class WalletDB extends EventEmitter { /** * Get a wallet from the database without a lock. * @private - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} - Returns {@link Wallet}. */ @@ -733,13 +758,19 @@ class WalletDB extends EventEmitter { if (cache) return cache; - const data = await this.db.get(layout.w.build(wid)); + const id = await this.getID(wid); - if (!data) + if (!id) return null; + const data = await this.db.get(layout.w.build(wid)); + assert(data); + const wallet = Wallet.fromRaw(this, data); + wallet.wid = wid; + wallet.id = id; + await wallet.open(); this.register(wallet); @@ -757,6 +788,7 @@ class WalletDB extends EventEmitter { const id = wallet.id; b.put(layout.w.build(wid), wallet.toRaw()); + b.put(layout.W.build(wid), fromString(id)); b.put(layout.l.build(id), fromU32(wid)); } @@ -801,16 +833,16 @@ class WalletDB extends EventEmitter { if (await this.has(id)) throw new Error('WDB: ID not available.'); - const old = wallet.id; const b = this.db.batch(); - b.del(layout.l.build(old)); + // Update wid->id index. + b.put(layout.W.build(wallet.wid), fromString(id)); - wallet.id = id; + // Delete old id->wid index. + b.del(layout.l.build(wallet.id)); - this.save(b, wallet); - - wallet.id = old; + // Add new id->wid index. + b.put(layout.l.build(id), fromU32(wallet.wid)); await b.write(); @@ -824,24 +856,31 @@ class WalletDB extends EventEmitter { */ renameAccount(b, account, name) { + const wid = account.wid; + const index = account.accountIndex; + // Remove old wid/name->account index. - b.del(layout.i.build(account.wid, account.name)); + b.del(layout.i.build(wid, account.name)); + + // Name->Index lookups + b.put(layout.i.build(wid, name), fromU32(index)); + + // Index->Name lookups + b.put(layout.n.build(wid, index), fromString(name)); account.name = name; - - this.saveAccount(b, account); } /** * Remove a wallet. - * @param {WalletID} wid + * @param {Number|String} id * @returns {Promise} */ async remove(id) { - const wid = await this.getWID(id); + const wid = await this.ensureWID(id); - if (!wid) + if (wid === -1) return false; // Grab all locks. @@ -861,24 +900,23 @@ class WalletDB extends EventEmitter { /** * Remove a wallet (without a lock). * @private - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} */ async _remove(wid) { - if (wid === 1) - throw new Error('Cannot remove primary wallet.'); + const id = await this.getID(wid); - const data = await this.db.get(layout.w.build(wid)); - - if (!data) + if (!id) return false; - const {id} = Wallet.fromRaw(this, data); + if (id === 'primary') + throw new Error('Cannot remove primary wallet.'); const b = this.db.batch(); b.del(layout.w.build(wid)); + b.del(layout.W.build(wid)); b.del(layout.l.build(id)); const piter = this.db.iterator({ @@ -969,13 +1007,13 @@ class WalletDB extends EventEmitter { /** * Get a wallet with token auth first. - * @param {WalletID} wid + * @param {Number|String} id * @param {Buffer} token * @returns {Promise} - Returns {@link Wallet}. */ - async auth(wid, token) { - const wallet = await this.get(wid); + async auth(id, token) { + const wallet = await this.get(id); if (!wallet) return null; @@ -1014,14 +1052,16 @@ class WalletDB extends EventEmitter { */ async _create(options) { - if (await this.has(options.id)) - throw new Error('WDB: Wallet already exists.'); + if (options.id) { + if (await this.has(options.id)) + throw new Error('WDB: Wallet already exists.'); + } const wallet = Wallet.fromOptions(this, options); wallet.wid = this.depth; - await wallet.init(options); + await wallet.init(options, options.passphrase); this.depth += 1; @@ -1034,13 +1074,13 @@ class WalletDB extends EventEmitter { /** * Test for the existence of a wallet. - * @param {WalletID} id + * @param {Number|String} id * @returns {Promise} */ async has(id) { - const wid = await this.getWID(id); - return wid != null; + const wid = await this.ensureWID(id); + return wid !== -1; } /** @@ -1050,10 +1090,12 @@ class WalletDB extends EventEmitter { */ async ensure(options) { - const wallet = await this.get(options.id); + if (options.id) { + const wallet = await this.get(options.id); - if (wallet) - return wallet; + if (wallet) + return wallet; + } return this.create(options); } @@ -1061,23 +1103,31 @@ class WalletDB extends EventEmitter { /** * Get an account from the database by wid. * @private - * @param {WalletID} wid + * @param {Number} wid * @param {Number} index - Account index. * @returns {Promise} - Returns {@link Wallet}. */ async getAccount(wid, index) { - const data = await this.db.get(layout.a.build(wid, index)); + const name = await this.getAccountName(wid, index); - if (!data) + if (!name) return null; - return Account.fromRaw(this, data); + const data = await this.db.get(layout.a.build(wid, index)); + assert(data); + + const account = Account.fromRaw(this, data); + + account.accountIndex = index; + account.name = name; + + return account; } /** * List account names and indexes from the db. - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} - Returns Array. */ @@ -1085,13 +1135,13 @@ class WalletDB extends EventEmitter { return this.db.values({ gte: layout.n.min(wid), lte: layout.n.max(wid), - parse: data => data.toString('ascii') + parse: toString }); } /** * Lookup the corresponding account name's index. - * @param {WalletID} wid + * @param {Number} wid * @param {String} name - Account name/index. * @returns {Promise} - Returns Number. */ @@ -1107,7 +1157,7 @@ class WalletDB extends EventEmitter { /** * Lookup the corresponding account index's name. - * @param {WalletID} wid + * @param {Number} wid * @param {Number} index * @returns {Promise} - Returns Number. */ @@ -1118,7 +1168,7 @@ class WalletDB extends EventEmitter { if (!name) return null; - return name.toString('ascii'); + return toString(name); } /** @@ -1144,7 +1194,7 @@ class WalletDB extends EventEmitter { /** * Test for the existence of an account. - * @param {WalletID} wid + * @param {Number} wid * @param {String|Number} acct * @returns {Promise} - Returns Boolean. */ @@ -1190,7 +1240,7 @@ class WalletDB extends EventEmitter { /** * Retrieve path by hash. - * @param {WalletID} wid + * @param {Number} wid * @param {Hash} hash * @returns {Promise} */ @@ -1209,7 +1259,7 @@ class WalletDB extends EventEmitter { /** * Retrieve path by hash. - * @param {WalletID} wid + * @param {Number} wid * @param {Hash} hash * @returns {Promise} */ @@ -1228,7 +1278,7 @@ class WalletDB extends EventEmitter { /** * Test whether a wallet contains a path. - * @param {WalletID} wid + * @param {Number} wid * @param {Hash} hash * @returns {Promise} */ @@ -1268,7 +1318,7 @@ class WalletDB extends EventEmitter { /** * Get all address hashes. - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} */ @@ -1282,7 +1332,7 @@ class WalletDB extends EventEmitter { /** * Get all account address hashes. - * @param {WalletID} wid + * @param {Number} wid * @param {Number} account * @returns {Promise} */ @@ -1297,7 +1347,7 @@ class WalletDB extends EventEmitter { /** * Get all paths for a wallet. - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} */ @@ -1329,16 +1379,16 @@ class WalletDB extends EventEmitter { */ async getWallets() { - return this.db.keys({ - gte: layout.l.min(), - lte: layout.l.max(), - parse: key => layout.l.parse(key) + return this.db.values({ + gte: layout.W.min(), + lte: layout.W.max(), + parse: toString }); } /** * Encrypt all imported keys for a wallet. - * @param {WalletID} wid + * @param {Number} wid * @param {Buffer} key * @returns {Promise} */ @@ -1371,7 +1421,7 @@ class WalletDB extends EventEmitter { /** * Decrypt all imported keys for a wallet. - * @param {WalletID} wid + * @param {Number} wid * @param {Buffer} key * @returns {Promise} */ @@ -1423,7 +1473,7 @@ class WalletDB extends EventEmitter { /** * Resend all pending transactions for a specific wallet. * @private - * @param {WalletID} wid + * @param {Number} wid * @returns {Promise} */ @@ -2273,7 +2323,16 @@ function fromU32(num) { } function fromString(str) { - return Buffer.from(str, 'ascii'); + const buf = Buffer.alloc(1 + str.length); + buf[0] = str.length; + buf.write(str, 1, str.length, 'ascii'); + return buf; +} + +function toString(buf) { + assert(buf.length > 0); + assert(buf[0] === buf.length - 1); + return buf.toString('ascii', 1, buf.length); } /* diff --git a/migrate/walletdb6to7.js b/migrate/walletdb6to7.js index c2a80cae..16bafb64 100644 --- a/migrate/walletdb6to7.js +++ b/migrate/walletdb6to7.js @@ -459,8 +459,6 @@ async function updateWallet(wid) { // Concatenate wallet with key. const bw = bio.write(); - bw.writeU32(wid); - bw.writeVarString(id, 'ascii'); bw.writeU8(flags); bw.writeU32(accountDepth); bw.writeBytes(token); @@ -468,6 +466,7 @@ async function updateWallet(wid) { bw.writeBytes(key); parent.put(layout.w.build(wid), bw.render()); + parent.put(layout.W.build(wid), fromString(id)); console.log('Updating accounts for %d...', wid); @@ -534,8 +533,6 @@ async function updateAccount(wid, acct) { if (witness) flags |= 2; - bw.writeU32(accountIndex); - bw.writeVarString(name, 'ascii'); bw.writeU8(flags); bw.writeU8(type); bw.writeU8(m); @@ -562,6 +559,7 @@ async function updateAccount(wid, acct) { } parent.put(layout.a.build(wid, acct), bw.render()); + parent.put(layout.n.build(wid, acct), fromString(name)); console.log('Updated account: %d/%d.', wid, acct); } @@ -896,6 +894,13 @@ function parsei(key) { // i[wid][name] return [key.readUInt32BE(1, true), key.toString('ascii', 5)]; } +function fromString(str) { + const buf = Buffer.alloc(1 + str.length); + buf[0] = str.length; + buf.write(str, 1, str.length, 'ascii'); + return buf; +} + /* * Execute */