From 16204a47946440b18deb83d13b1ae727ea945bbd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 15 Aug 2016 09:52:12 -0700 Subject: [PATCH] walletdb: use layout for walletdb and txdb. --- lib/bcoin/txdb.js | 478 ++++++++++++++++++++++++++++-------------- lib/bcoin/walletdb.js | 110 ++++++---- test/wallet-test.js | 2 +- 3 files changed, 396 insertions(+), 194 deletions(-) diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index b0cd70b5..342f583f 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -7,6 +7,15 @@ 'use strict'; +var bcoin = require('./env'); +var utils = require('./utils'); +var assert = bcoin.utils.assert; +var constants = bcoin.protocol.constants; +var DUMMY = new Buffer([0]); +var pad32 = utils.pad32; +var BufferReader = require('./reader'); +var BufferWriter = require('./writer'); + /* * Database Layout: * t/[hash] -> extended tx @@ -24,14 +33,163 @@ * C/[account]/[hash]/[index] -> dummy (coin by account) */ -var bcoin = require('./env'); -var utils = require('./utils'); -var assert = bcoin.utils.assert; -var constants = bcoin.protocol.constants; -var DUMMY = new Buffer([0]); -var pad32 = utils.pad32; -var BufferReader = require('./reader'); -var BufferWriter = require('./writer'); +function Layout(wallet) { + this.wallet = wallet; +} + +Layout.prototype.prefix = function prefix(key) { + assert(this.wallet.wid); + return 't' + pad32(this.wallet.wid) + key; +}; + +Layout.prototype.hi = function hi(ch, hash, index) { + return this.prefix(ch + hash + pad32(index)); +}; + +Layout.prototype.hii = function hii(key) { + key = key.slice(12); + return [key.slice(0, 64), +key.slice(64)]; +}; + +Layout.prototype.ih = function ih(ch, index, hash) { + return this.prefix(ch + pad32(index) + hash); +}; + +Layout.prototype.ihh = function ihh(key) { + key = key.slice(12); + return [+key.slice(0, 10), key.slice(10)]; +}; + +Layout.prototype.iih = function iih(ch, index, num, hash) { + return this.prefix(ch + pad32(index) + pad32(num) + hash); +}; + +Layout.prototype.iihh = function iihh(key) { + key = key.slice(12); + return [+key.slice(0, 10), +key.slice(10, 20), key.slice(20)]; +}; + +Layout.prototype.ihi = function ihi(ch, index, hash, num) { + return this.prefix(ch + pad32(index) + hash + pad32(num)); +}; + +Layout.prototype.ihii = function ihii(key) { + key = key.slice(12); + return [+key.slice(0, 10), key.slice(10, 74), +key.slice(74)]; +}; + +Layout.prototype.ha = function ha(ch, hash) { + return this.prefix(ch + hash); +}; + +Layout.prototype.haa = function haa(key) { + key = key.slice(12); + return key; +}; + +Layout.prototype.t = function t(hash) { + return this.ha('t', hash); +}; + +Layout.prototype.tt = function tt(key) { + return this.haa(key); +}; + +Layout.prototype.c = function c(hash, index) { + return this.hi('c', hash, index); +}; + +Layout.prototype.cc = function cc(key) { + return this.hii(key); +}; + +Layout.prototype.d = function d(hash, index) { + return this.hi('d', hash, index); +}; + +Layout.prototype.dd = function dd(key) { + return this.hii(key); +}; + +Layout.prototype.s = function s(hash, index) { + return this.hi('s', hash, index); +}; + +Layout.prototype.ss = function ss(key) { + return this.hii(key); +}; + +Layout.prototype.o = function o(hash, index) { + return this.hi('o', hash, index); +}; + +Layout.prototype.oo = function oo(key) { + return this.hii(key); +}; + +Layout.prototype.p = function p(hash) { + return this.ha('p', hash); +}; + +Layout.prototype.pp = function pp(key) { + return this.haa(key); +}; + +Layout.prototype.m = function m(time, hash) { + return this.ih('m', time, hash); +}; + +Layout.prototype.mm = function mm(key) { + return this.ihh(key); +}; + +Layout.prototype.h = function h(height, hash) { + return this.ih('h', height, hash); +}; + +Layout.prototype.hh = function hh(key) { + return this.ihh(key); +}; + +Layout.prototype.T = function T(account, hash) { + return this.ih('T', account, hash); +}; + +Layout.prototype.Tt = function Tt(key) { + return this.ihh(key); +}; + +Layout.prototype.P = function P(account, hash) { + return this.ih('P', account, hash); +}; + +Layout.prototype.Pp = function Pp(key) { + return this.ihh(key); +}; + +Layout.prototype.M = function M(account, time, hash) { + return this.iih('M', account, time, hash); +}; + +Layout.prototype.Mm = function Mm(key) { + return this.iihh(key); +}; + +Layout.prototype.H = function H(account, height, hash) { + return this.iih('H', account, height, hash); +}; + +Layout.prototype.Hh = function Hh(key) { + return this.iihh(key); +}; + +Layout.prototype.C = function C(account, hash, index) { + return this.ihi('C', account, hash, index); +}; + +Layout.prototype.Cc = function Cc(key) { + return this.ihii(key); +}; /** * TXDB @@ -50,6 +208,7 @@ function TXDB(wallet) { this.logger = wallet.db.logger; this.network = wallet.db.network; this.options = wallet.db.options; + this.key = new Layout(wallet); this.locker = new bcoin.locker(this); this.coinCache = new bcoin.lru(10000, 1); @@ -83,16 +242,6 @@ TXDB.prototype.open = function open(callback) { }); }; -/** - * Compile wallet prefix. - * @param {String} key - */ - -TXDB.prototype.prefix = function prefix(key) { - assert(this.wallet.wid); - return 't/' + pad32(this.wallet.wid) + '/' + key; -}; - /** * Emit transaction event. * @private @@ -135,7 +284,7 @@ TXDB.prototype.start = function start() { TXDB.prototype.put = function put(key, value) { assert(this.current); - this.current.put(this.prefix(key), value); + this.current.put(key, value); }; /** @@ -145,7 +294,7 @@ TXDB.prototype.put = function put(key, value) { TXDB.prototype.del = function del(key) { assert(this.current); - this.current.del(this.prefix(key)); + this.current.del(key); }; /** @@ -175,7 +324,7 @@ TXDB.prototype.drop = function drop() { */ TXDB.prototype.fetch = function fetch(key, parse, callback) { - this.db.fetch(this.prefix(key), parse, callback); + this.db.fetch(key, parse, callback); }; /** @@ -184,7 +333,7 @@ TXDB.prototype.fetch = function fetch(key, parse, callback) { */ TXDB.prototype.get = function get(key, callback) { - this.db.get(this.prefix(key), callback); + this.db.get(key, callback); }; /** @@ -193,7 +342,7 @@ TXDB.prototype.get = function get(key, callback) { */ TXDB.prototype.has = function has(key, callback) { - this.db.has(this.prefix(key), callback); + this.db.has(key, callback); }; /** @@ -203,10 +352,6 @@ TXDB.prototype.has = function has(key, callback) { */ TXDB.prototype.iterate = function iterate(options, callback) { - if (options.gte) - options.gte = this.prefix(options.gte); - if (options.lte) - options.lte = this.prefix(options.lte); this.db.iterate(options, callback); }; @@ -248,12 +393,12 @@ TXDB.prototype.getInfo = function getInfo(tx, callback) { * @param {Function} callback - Returns [Error, Buffer]. */ -TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { +TXDB.prototype._addOrphan = function _addOrphan(hash, index, outpoint, callback) { var self = this; var p = new BufferWriter(); - var k = 'o/' + key; + var key = this.key.o(hash, index); - this.get(k, function(err, data) { + this.get(key, function(err, data) { if (err) return callback(err); @@ -262,7 +407,7 @@ TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { p.writeBytes(outpoint); - self.put(k, p.render()); + self.put(key, p.render()); return callback(); }); @@ -275,11 +420,11 @@ TXDB.prototype._addOrphan = function _addOrphan(key, outpoint, callback) { * @param {Function} callback - Returns [Error, {@link Orphan}]. */ -TXDB.prototype._getOrphans = function _getOrphans(key, callback) { +TXDB.prototype._getOrphans = function _getOrphans(hash, index, callback) { var self = this; var items = []; - this.fetch('o/' + key, function(data) { + this.fetch(this.key.o(hash, index), function(data) { var p = new BufferReader(data); var orphans = []; @@ -416,17 +561,16 @@ TXDB.prototype._verify = function _verify(tx, info, callback) { TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index, callback) { var self = this; var hash = tx.hash('hex'); - var key = hash + '/' + pad32(index); var coin; - this._getOrphans(key, function(err, orphans) { + this._getOrphans(hash, index, function(err, orphans) { if (err) return callback(err); if (!orphans) return callback(null, false); - self.del('o/' + key); + self.del(self.key.o(hash, index)); coin = bcoin.coin.fromTX(tx, index); @@ -447,7 +591,7 @@ TXDB.prototype._resolveOrphans = function _resolveOrphans(tx, index, callback) { // Verify that input script is correct, if not - add // output to unspent and remove orphan from storage if (!self.options.verify || orphan.verifyInput(input.index)) { - self.put('d/' + input.hash + '/' + pad32(input.index), coin.toRaw()); + self.put(self.key.d(input.hash, input.index), coin.toRaw()); return callback(null, true); } @@ -505,23 +649,23 @@ TXDB.prototype.add = function add(tx, info, callback) { hash = tx.hash('hex'); self.start(); - self.put('t/' + hash, tx.toExtended()); + self.put(self.key.t(hash), tx.toExtended()); if (tx.ts === 0) - self.put('p/' + hash, DUMMY); + self.put(self.key.p(hash), DUMMY); else - self.put('h/' + pad32(tx.height) + '/' + hash, DUMMY); + self.put(self.key.h(tx.height, hash), DUMMY); - self.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); + self.put(self.key.m(tx.ps, hash), DUMMY); for (i = 0; i < info.accounts.length; i++) { - account = pad32(info.accounts[i]); - self.put('T/' + account + '/' + hash, DUMMY); + account = info.accounts[i]; + self.put(self.key.T(account, hash), DUMMY); if (tx.ts === 0) - self.put('P/' + account + '/' + hash, DUMMY); + self.put(self.key.P(account, hash), DUMMY); else - self.put('H/' + account + '/' + pad32(tx.height) + '/' + hash, DUMMY); - self.put('M/' + account + '/' + pad32(tx.ps) + '/' + hash, DUMMY); + self.put(self.key.H(account, tx.height, hash), DUMMY); + self.put(self.key.M(account, tx.ps, hash), DUMMY); } // Consume unspent money or add orphans @@ -539,19 +683,19 @@ TXDB.prototype.add = function add(tx, info, callback) { if (!path) return next(); - key = prevout.hash + '/' + pad32(prevout.index); + key = prevout.hash + '/' + prevout.index; // s/[outpoint-key] -> [spender-hash]|[spender-input-index] outpoint = bcoin.outpoint.fromTX(tx, i).toRaw(); - self.put('s/' + key, outpoint); + self.put(self.key.s(prevout.hash, prevout.index), outpoint); // Add orphan, if no parent transaction is yet known if (!input.coin) - return self._addOrphan(key, outpoint, next); + return self._addOrphan(prevout.hash, prevout.index, outpoint, next); - self.del('c/' + key); - self.del('C/' + pad32(path.account) + '/' + key); - self.put('d/' + hash + '/' + pad32(i), input.coin.toRaw()); + self.del(self.key.c(prevout.hash, prevout.index)); + self.del(self.key.C(path.account, prevout.hash, prevout.index)); + self.put(self.key.d(hash, i), input.coin.toRaw()); self.balance.sub(input.coin); self.coinCache.remove(key); @@ -566,7 +710,7 @@ TXDB.prototype.add = function add(tx, info, callback) { // Add unspent outputs or resolve orphans utils.forEachSerial(tx.outputs, function(output, next, i) { var address = output.getHash('hex'); - var key = hash + '/' + pad32(i); + var key = hash + '/' + i; var coin; path = info.getPath(address); @@ -586,8 +730,8 @@ TXDB.prototype.add = function add(tx, info, callback) { self.balance.add(coin); coin = coin.toRaw(); - self.put('c/' + key, coin); - self.put('C/' + pad32(path.account) + '/' + key, DUMMY); + self.put(self.key.c(hash, i), coin); + self.put(self.key.C(path.account, hash, i), DUMMY); self.coinCache.set(key, coin); @@ -753,7 +897,7 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { */ TXDB.prototype.isSpent = function isSpent(hash, index, callback) { - var key = 's/' + hash + '/' + pad32(index); + var key = this.key.s(hash, index); this.fetch(key, function(data) { return bcoin.outpoint.fromRaw(data); }, callback); @@ -800,20 +944,20 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) { self.start(); - self.put('t/' + hash, tx.toExtended()); + self.put(self.key.t(hash), tx.toExtended()); - self.del('p/' + hash); - self.put('h/' + pad32(tx.height) + '/' + hash, DUMMY); + self.del(self.key.p(hash)); + self.put(self.key.h(tx.height, hash), DUMMY); for (i = 0; i < info.accounts.length; i++) { - account = pad32(info.accounts[i]); - self.del('P/' + account + '/' + hash); - self.put('H/' + account + '/' + pad32(tx.height) + '/' + hash, DUMMY); + account = info.accounts[i]; + self.del(self.key.P(account, hash)); + self.put(self.key.H(account, tx.height, hash), DUMMY); } utils.forEachSerial(tx.outputs, function(output, next, i) { var address = output.getHash('hex'); - var key = hash + '/' + pad32(i); + var key = hash + '/' + i; // Only update coins if this output is ours. if (!info.hasPath(address)) @@ -833,7 +977,7 @@ TXDB.prototype._confirm = function _confirm(tx, info, callback) { coin.height = tx.height; coin = coin.toRaw(); - self.put('c/' + key, coin); + self.put(self.key.c(hash, i), coin); self.coinCache.set(key, coin); @@ -914,23 +1058,23 @@ TXDB.prototype._remove = function remove(tx, info, callback) { var hash = tx.hash('hex'); var i, path, account, key, address, input, output, coin; - this.del('t/' + hash); + this.del(this.key.t(hash)); if (tx.ts === 0) - this.del('p/' + hash); + this.del(this.key.p(hash)); else - this.del('h/' + pad32(tx.height) + '/' + hash); + this.del(this.key.h(tx.height, hash)); - this.del('m/' + pad32(tx.ps) + '/' + hash); + this.del(this.key.m(tx.ps, hash)); for (i = 0; i < info.accounts.length; i++) { - account = pad32(info.accounts[i]); - this.del('T/' + account + '/' + hash); + account = info.accounts[i]; + this.del(this.key.T(account, hash)); if (tx.ts === 0) - this.del('P/' + account + '/' + hash); + this.del(this.key.P(account, hash)); else - this.del('H/' + account + '/' + pad32(tx.height) + '/' + hash); - this.del('M/' + account + '/' + pad32(tx.ps) + '/' + hash); + this.del(this.key.H(account, tx.height, hash)); + this.del(this.key.M(account, tx.ps, hash)); } this.fillHistory(tx, function(err) { @@ -939,7 +1083,8 @@ TXDB.prototype._remove = function remove(tx, info, callback) { for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; - key = input.prevout.hash + '/' + pad32(input.prevout.index); + key = input.prevout.hash + '/' + input.prevout.index; + var prevout = input.prevout; address = input.getHash('hex'); if (tx.isCoinbase()) @@ -957,18 +1102,18 @@ TXDB.prototype._remove = function remove(tx, info, callback) { coin = input.coin.toRaw(); - self.put('c/' + key, coin); - self.put('C/' + pad32(path.account) + '/' + key, DUMMY); - self.del('d/' + hash + '/' + pad32(i)); - self.del('s/' + key); - self.del('o/' + key); + self.put(self.key.c(prevout.hash, prevout.index), coin); + self.put(self.key.C(path.account, prevout.hash, prevout.index), DUMMY); + self.del(self.key.d(hash, i)); + self.del(self.key.s(prevout.hash, prevout.index)); + self.del(self.key.o(prevout.hash, prevout.index)); self.coinCache.set(key, coin); } for (i = 0; i < tx.outputs.length; i++) { output = tx.outputs[i]; - key = hash + '/' + pad32(i); + key = hash + '/' + i; address = output.getHash('hex'); path = info.getPath(address); @@ -980,8 +1125,8 @@ TXDB.prototype._remove = function remove(tx, info, callback) { self.balance.sub(coin); - self.del('c/' + key); - self.del('C/' + pad32(path.account) + '/' + key); + self.del(self.key.c(hash, i)); + self.del(self.key.C(path.account, hash, i)); self.coinCache.remove(key); } @@ -1060,19 +1205,19 @@ TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) { tx.index = -1; tx.block = null; - this.put('t/' + hash, tx.toExtended()); + this.put(this.key.t(hash), tx.toExtended()); - this.put('p/' + hash, DUMMY); - this.del('h/' + pad32(height) + '/' + hash); + this.put(this.key.p(hash), DUMMY); + this.del(this.key.h(height, hash)); for (i = 0; i < info.accounts.length; i++) { - account = pad32(info.accounts[i]); - this.put('P/' + account + '/' + hash, DUMMY); - this.del('H/' + account + '/' + pad32(height) + '/' + hash); + account = info.accounts[i]; + this.put(this.key.P(account, hash), DUMMY); + this.del(this.key.H(account, height, hash)); } utils.forEachSerial(tx.outputs, function(output, next, i) { - var key = hash + '/' + pad32(i); + var key = hash + '/' + i; self.getCoin(hash, i, function(err, coin) { if (err) return next(err); @@ -1086,7 +1231,7 @@ TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) { coin.height = tx.height; coin = coin.toRaw(); - self.put('c/' + key, coin); + self.put(self.key.c(hash, i), coin); self.coinCache.set(key, coin); @@ -1109,6 +1254,8 @@ TXDB.prototype._unconfirm = function unconfirm(tx, info, callback, force) { */ TXDB.prototype.getHistoryHashes = function getHistoryHashes(account, callback) { + var self = this; + if (typeof account === 'function') { callback = account; account = null; @@ -1116,16 +1263,18 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(account, callback) { this.iterate({ gte: account != null - ? 'T/' + pad32(account) + '/' + constants.NULL_HASH - : 't/' + constants.NULL_HASH, + ? this.key.T(account, constants.NULL_HASH) + : this.key.t(constants.NULL_HASH), lte: account != null - ? 'T/' + pad32(account) + '/' + constants.HIGH_HASH - : 't/' + constants.HIGH_HASH, + ? this.key.T(account, constants.HIGH_HASH) + : this.key.t(constants.HIGH_HASH), transform: function(key) { - key = key.split('/'); - if (account != null) - return key[4]; - return key[3]; + if (account != null) { + key = self.key.Tt(key); + return key[1]; + } + key = self.key.tt(key); + return key[0]; } }, callback); }; @@ -1137,6 +1286,8 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(account, callback) { */ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account, callback) { + var self = this; + if (typeof account === 'function') { callback = account; account = null; @@ -1144,16 +1295,18 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account, cal this.iterate({ gte: account != null - ? 'P/' + pad32(account) + '/' + constants.NULL_HASH - : 'p/' + constants.NULL_HASH, + ? this.key.P(account, constants.NULL_HASH) + : this.key.p(constants.NULL_HASH), lte: account != null - ? 'P/' + pad32(account) + '/' + constants.HIGH_HASH - : 'p/' + constants.HIGH_HASH, + ? this.key.P(account, constants.HIGH_HASH) + : this.key.p(constants.HIGH_HASH), transform: function(key) { - key = key.split('/'); - if (account != null) - return key[4]; - return key[3]; + if (account != null) { + key = self.key.Pp(key); + return key[1]; + } + key = self.key.pp(key); + return key[0]; } }, callback); }; @@ -1165,6 +1318,8 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(account, cal */ TXDB.prototype.getCoinHashes = function getCoinHashes(account, callback) { + var self = this; + if (typeof account === 'function') { callback = account; account = null; @@ -1172,16 +1327,18 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(account, callback) { this.iterate({ gte: account != null - ? 'C/' + pad32(account) + '/' + constants.NULL_HASH + '/' + pad32(0) - : 'c/' + constants.NULL_HASH + '/' + pad32(0), + ? this.key.C(account, constants.NULL_HASH, 0) + : this.key.c(constants.NULL_HASH, 0), lte: account != null - ? 'C/' + pad32(account) + '/' + constants.HIGH_HASH + '/' + pad32(0xffffffff) - : 'c/' + constants.HIGH_HASH + '/' + pad32(0xffffffff), + ? this.key.C(account, constants.HIGH_HASH, 0xffffffff) + : this.key.c(constants.HIGH_HASH, 0xffffffff), transform: function(key) { - key = key.split('/'); - if (account != null) - return [key[4], +key[5]]; - return [key[3], +key[4]]; + if (account != null) { + key = self.key.Cc(key); + return [key[1], key[2]]; + } + key = self.key.cc(key); + return key; } }, callback); }; @@ -1198,6 +1355,8 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(account, callback) { */ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(account, options, callback) { + var self = this; + if (typeof account !== 'number') { callback = options; options = account; @@ -1206,18 +1365,20 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(account, opt this.iterate({ gte: account != null - ? 'H/' + pad32(account) + '/' + pad32(options.start) + '/' + constants.NULL_HASH - : 'h/' + pad32(options.start) + '/' + constants.NULL_HASH, + ? this.key.H(account, options.start, constants.NULL_HASH) + : this.key.h(options.start, constants.NULL_HASH), lte: account != null - ? 'H/' + pad32(account) + '/' + pad32(options.end) + '/' + constants.HIGH_HASH - : 'h/' + pad32(options.end) + '/' + constants.HIGH_HASH, + ? this.key.H(account, options.end, constants.HIGH_HASH) + : this.key.h(options.end, constants.HIGH_HASH), limit: options.limit, reverse: options.reverse, transform: function(key) { - key = key.split('/'); - if (account != null) - return key[4]; - return key[3]; + if (account != null) { + key = self.key.Hh(key); + return key[2]; + } + key = self.key.hh(key); + return key[1]; } }, callback); }; @@ -1244,6 +1405,8 @@ TXDB.prototype.getHeightHashes = function getHeightHashes(height, callback) { */ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options, callback) { + var self = this; + if (typeof account === 'function') { callback = account; account = null; @@ -1251,18 +1414,20 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(account, options, callba this.iterate({ gte: account != null - ? 'M/' + pad32(account) + '/' + pad32(options.start) + '/' + constants.NULL_HASH - : 'm/' + pad32(options.start) + '/' + constants.NULL_HASH, + ? this.key.M(account, options.start, constants.NULL_HASH) + : this.key.m(options.start, constants.NULL_HASH), lte: account != null - ? 'M/' + pad32(account) + '/' + pad32(options.end) + '/' + constants.HIGH_HASH - : 'm/' + pad32(options.end) + '/' + constants.HIGH_HASH, + ? this.key.M(account, options.end, constants.HIGH_HASH) + : this.key.m(options.end, constants.HIGH_HASH), limit: options.limit, reverse: options.reverse, transform: function(key) { - key = key.split('/'); - if (account != null) - return key[4]; - return key[3]; + if (account != null) { + key = self.key.Mm(key); + return key[2]; + } + key = self.key.mm(key); + return key[1]; } }, callback); }; @@ -1352,8 +1517,8 @@ TXDB.prototype.getHistory = function getHistory(account, callback) { // Fast case this.iterate({ - gte: 't/' + constants.NULL_HASH, - lte: 't/' + constants.HIGH_HASH, + gte: this.key.t(constants.NULL_HASH), + lte: this.key.t(constants.HIGH_HASH), values: true, parse: function(data) { return bcoin.tx.fromExtended(data); @@ -1496,18 +1661,18 @@ TXDB.prototype.getCoins = function getCoins(account, callback) { // Fast case this.iterate({ - gte: 'c/' + constants.NULL_HASH + '/' + pad32(0), - lte: 'c/' + constants.HIGH_HASH + '/' + pad32(0xffffffff), + gte: this.key.c(constants.NULL_HASH, 0), + lte: this.key.c(constants.HIGH_HASH, 0xffffffff), keys: true, values: true, parse: function(data, key) { - var parts = key.split('/'); - var hash = parts[3]; - var index = +parts[4]; + var parts = self.key.cc(key); + var hash = parts[0]; + var index = parts[1]; var coin = bcoin.coin.fromRaw(data); coin.hash = hash; coin.index = index; - key = hash + '/' + pad32(index); + key = hash + '/' + index; self.coinCache.set(key, data); return coin; } @@ -1556,6 +1721,7 @@ TXDB.prototype.getAccountCoins = function getCoins(account, callback) { */ TXDB.prototype.fillHistory = function fillHistory(tx, callback) { + var self = this; var hash, index, coin, input; if (tx.isCoinbase()) { @@ -1566,12 +1732,12 @@ TXDB.prototype.fillHistory = function fillHistory(tx, callback) { hash = tx.hash('hex'); this.iterate({ - gte: 'd/' + hash + '/' + pad32(0), - lte: 'd/' + hash + '/' + pad32(0xffffffff), + gte: this.key.d(hash, 0), + lte: this.key.d(hash, 0xffffffff), keys: true, values: true, parse: function(value, key) { - index = +key.split('/')[4]; + index = self.key.dd(key)[1]; coin = bcoin.coin.fromRaw(value); input = tx.inputs[index]; coin.hash = input.prevout.hash; @@ -1628,7 +1794,7 @@ TXDB.prototype.fillCoins = function fillCoins(tx, callback) { */ TXDB.prototype.getTX = function getTX(hash, callback) { - this.fetch('t/' + hash, function(tx) { + this.fetch(this.key.t(hash), function(tx) { return bcoin.tx.fromExtended(tx); }, callback); }; @@ -1705,7 +1871,7 @@ TXDB.prototype.toDetails = function toDetails(tx, callback) { */ TXDB.prototype.hasTX = function hasTX(hash, callback) { - this.has('t/' + hash, callback); + this.has(this.key.t(hash), callback); }; /** @@ -1717,7 +1883,7 @@ TXDB.prototype.hasTX = function hasTX(hash, callback) { TXDB.prototype.getCoin = function getCoin(hash, index, callback) { var self = this; - var key = hash + '/' + pad32(index); + var key = hash + '/' + index; var coin = this.coinCache.get(key); if (coin) { @@ -1731,7 +1897,7 @@ TXDB.prototype.getCoin = function getCoin(hash, index, callback) { return callback(null, coin); } - this.fetch('c/' + key, function(data) { + this.fetch(this.key.c(hash, index), function(data) { coin = bcoin.coin.fromRaw(data); coin.hash = hash; coin.index = index; @@ -1747,12 +1913,12 @@ TXDB.prototype.getCoin = function getCoin(hash, index, callback) { */ TXDB.prototype.hasCoin = function hasCoin(hash, index, callback) { - var key = hash + '/' + pad32(index); + var key = hash + '/' + index; if (this.coinCache.has(key)) return callback(null, true); - this.has('c/' + key, callback); + this.has(this.key.c(hash, index), callback); }; /** @@ -1782,14 +1948,14 @@ TXDB.prototype.getBalance = function getBalance(account, callback) { balance = new Balance(this.wallet); this.iterate({ - gte: 'c/' + constants.NULL_HASH + '/' + pad32(0), - lte: 'c/' + constants.HIGH_HASH + '/' + pad32(0xffffffff), + gte: this.key.c(constants.NULL_HASH, 0), + lte: this.key.c(constants.HIGH_HASH, 0xffffffff), keys: true, values: true, parse: function(data, key) { - var parts = key.split('/'); - var hash = parts[3]; - var index = +parts[4]; + var parts = self.key.cc(key); + var hash = parts[0]; + var index = parts[1]; var height = data.readUInt32LE(4, true); var value = utils.read64N(data, 8); @@ -1802,7 +1968,7 @@ TXDB.prototype.getBalance = function getBalance(account, callback) { else balance.confirmed += value; - key = hash + '/' + pad32(index); + key = hash + '/' + index; self.coinCache.set(key, data); } @@ -1844,7 +2010,7 @@ TXDB.prototype.getAccountBalance = function getBalance(account, callback) { return callback(err); utils.forEachSerial(hashes, function(hash, next) { - key = hash[0] + '/' + pad32(hash[1]); + key = hash[0] + '/' + hash[1]; coin = self.coinCache.get(key); if (coin) { @@ -1856,7 +2022,7 @@ TXDB.prototype.getAccountBalance = function getBalance(account, callback) { return next(); } - self.get('c/' + key, function(err, data) { + self.get(self.key.c(hash[0], hash[1]), function(err, data) { if (err) return next(err); @@ -1932,7 +2098,7 @@ TXDB.prototype.zap = function zap(account, age, callback, force) { TXDB.prototype.abandon = function abandon(hash, callback, force) { var self = this; - this.has('p/' + hash, function(err, result) { + this.has(this.key.p(hash), function(err, result) { if (err) return callback(err); diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index a850062c..2c46f00a 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -17,7 +17,7 @@ * t/[wid]/* -> txdb * R -> tip * b/[hash] -> wallet block - * t/[hash] -> tx->wid map + * e/[hash] -> tx->wid map */ var bcoin = require('./env'); @@ -31,6 +31,43 @@ var BufferWriter = require('./writer'); var TXDB = require('./txdb'); var keyTypes = bcoin.keyring.types; +var layout = { + p: function(hash) { + return 'p' + hash; + }, + pp: function(key) { + return key.slice(1); + }, + w: function(wid) { + return 'w' + pad32(wid); + }, + ww: function(key) { + return +key.slice(1); + }, + l: function(id) { + return 'l' + id; + }, + ll: function(key) { + return key.slice(1); + }, + a: function a(wid, index) { + return 'a' + pad32(wid) + pad32(index); + }, + i: function i(wid, name) { + return 'i' + pad32(wid) + name; + }, + ii: function ii(key) { + return [+key.slice(1, 11), key.slice(11)]; + }, + R: 'R', + b: function b(hash) { + return 'b' + hash; + }, + e: function e(hash) { + return 'e' + hash; + } +}; + /** * WalletDB * @exports WalletDB @@ -194,8 +231,8 @@ WalletDB.prototype.getDepth = function getDepth(callback) { // walletdb.create by simply seeking to the // highest wallet wid. iter = this.db.iterator({ - gte: 'w/' + pad32(0), - lte: 'w/' + pad32(0xffffffff), + gte: layout.w(0), + lte: layout.w(0xffffffff), reverse: true, keys: true, values: false, @@ -218,8 +255,7 @@ WalletDB.prototype.getDepth = function getDepth(callback) { if (key === undefined) return callback(null, 1); - parts = key.split('/'); - depth = +parts[1]; + depth = layout.ww(key); callback(null, depth + 1); }); @@ -291,10 +327,10 @@ WalletDB.prototype.loadFilter = function loadFilter(callback) { return callback(); this.db.iterate({ - gte: 'p/' + constants.NULL_HASH, - lte: 'p/' + constants.HIGH_HASH, + gte: layout.p(constants.NULL_HASH), + lte: layout.p(constants.HIGH_HASH), transform: function(key) { - key = key.split('/')[1]; + key = layout.pp(key); self.filter.add(key, 'hex'); } }, callback); @@ -385,7 +421,7 @@ WalletDB.prototype.getWalletID = function getWalletID(id, callback) { if (wid) return callback(null, wid); - this.db.fetch('l/' + id, function(data) { + this.db.fetch(layout.l(id), function(data) { wid = data.readUInt32LE(0, true); self.walletCache.set(id, wid); return wid; @@ -457,7 +493,7 @@ WalletDB.prototype._get = function get(wid, callback) { if (wallet) return callback(null, wallet, true); - this.db.fetch('w/' + pad32(wid), function(data) { + this.db.fetch(layout.w(wid), function(data) { return bcoin.wallet.fromRaw(self, data); }, callback); }; @@ -472,9 +508,9 @@ WalletDB.prototype.save = function save(wallet) { var batch = this.batch(wallet.wid); var wid = new Buffer(4); this.walletCache.set(wallet.id, wallet.wid); - batch.put('w/' + pad32(wallet.wid), wallet.toRaw()); + batch.put(layout.w(wallet.wid), wallet.toRaw()); wid.writeUInt32LE(wallet.wid, 0, true); - batch.put('l/' + wallet.id, wid); + batch.put(layout.l(wallet.id), wid); }; /** @@ -646,7 +682,7 @@ WalletDB.prototype._getAccount = function getAccount(wid, index, callback) { if (account) return callback(null, account); - this.db.fetch('a/' + key, function(data) { + this.db.fetch(layout.a(wid, index), function(data) { account = bcoin.account.fromRaw(self, data); self.accountCache.set(key, account); return account; @@ -664,11 +700,11 @@ WalletDB.prototype.getAccounts = function getAccounts(wid, callback) { var i, accounts; this.db.iterate({ - gte: 'i/' + pad32(wid) + '/', - lte: 'i/' + pad32(wid) + '/~', + gte: layout.i(wid, ''), + lte: layout.i(wid, '~'), values: true, parse: function(value, key) { - var name = key.split('/')[2]; + var name = layout.ii(key)[1]; var index = value.readUInt32LE(0, true); map[index] = name; } @@ -705,7 +741,7 @@ WalletDB.prototype.getAccountIndex = function getAccountIndex(wid, name, callbac if (typeof name === 'number') return callback(null, name); - this.db.get('i/' + pad32(wid) + '/' + name, function(err, index) { + this.db.get(layout.i(wid, name), function(err, index) { if (err) return callback(err); @@ -729,8 +765,8 @@ WalletDB.prototype.saveAccount = function saveAccount(account) { index.writeUInt32LE(account.accountIndex, 0, true); - batch.put('a/' + key, account.toRaw()); - batch.put('i/' + pad32(account.wid) + '/' + account.name, index); + batch.put(layout.a(account.wid, account.accountIndex), account.toRaw()); + batch.put(layout.i(account.wid, account.name), index); this.accountCache.set(key, account); }; @@ -801,7 +837,7 @@ WalletDB.prototype.hasAccount = function hasAccount(wid, account, callback) { if (self.accountCache.has(key)) return callback(null, true); - self.db.has('a/' + key, callback); + self.db.has(layout.a(wid, index), callback); }); }; @@ -854,7 +890,7 @@ WalletDB.prototype.saveAddress = function saveAddress(wid, addresses, callback) self.pathCache.set(hash, paths); - batch.put('p/' + hash, serializePaths(paths)); + batch.put(layout.p(hash), serializePaths(paths)); next(); }); @@ -879,7 +915,7 @@ WalletDB.prototype.getPaths = function getPaths(hash, callback) { if (paths) return callback(null, paths); - this.db.fetch('p/' + hash, parsePaths, function(err, paths) { + this.db.fetch(layout.p(hash), parsePaths, function(err, paths) { if (err) return callback(err); @@ -925,8 +961,8 @@ WalletDB.prototype.getAddresses = function getAddresses(wid, callback) { } this.db.iterate({ - gte: 'p/' + constants.NULL_HASH, - lte: 'p/' + constants.HIGH_HASH, + gte: layout.p(constants.NULL_HASH), + lte: layout.p(constants.HIGH_HASH), values: true, parse: function(value, key) { var paths = parsePaths(value); @@ -934,7 +970,7 @@ WalletDB.prototype.getAddresses = function getAddresses(wid, callback) { if (wid && !paths[wid]) return; - return key.split('/')[1]; + return layout.pp(key); } }, callback); }; @@ -946,10 +982,10 @@ WalletDB.prototype.getAddresses = function getAddresses(wid, callback) { WalletDB.prototype.getWallets = function getWallets(callback) { this.db.iterate({ - gte: 'l/', - lte: 'l/~', + gte: layout.l(''), + lte: layout.l('~'), transform: function(key) { - return key.split('/')[1]; + return layout.ll(key); } }, callback); }; @@ -1126,7 +1162,7 @@ WalletDB.prototype.writeGenesis = function writeGenesis(callback) { */ WalletDB.prototype.getTip = function getTip(callback) { - this.db.fetch('R', function(data) { + this.db.fetch(layout.R, function(data) { return WalletBlock.fromTip(data); }, callback); }; @@ -1141,7 +1177,7 @@ WalletDB.prototype.getTip = function getTip(callback) { WalletDB.prototype.setTip = function setTip(hash, height, callback) { var self = this; var block = new WalletBlock(hash, height); - this.db.put('R', block.toTip(), function(err) { + this.db.put(layout.R, block.toTip(), function(err) { if (err) return callback(err); @@ -1163,15 +1199,15 @@ WalletDB.prototype.writeBlock = function writeBlock(block, matches, callback) { var batch = this.db.batch(); var i, hash, wallets; - batch.put('R', block.toTip()); + batch.put(layout.R, block.toTip()); if (block.hashes.length > 0) { - batch.put('b/' + block.hash, block.toRaw()); + batch.put(layout.b(block.hash), block.toRaw()); for (i = 0; i < block.hashes.length; i++) { hash = block.hashes[i]; wallets = matches[i]; - batch.put('t/' + hash, serializeWallets(wallets)); + batch.put(layout.e(hash), serializeWallets(wallets)); } } @@ -1189,8 +1225,8 @@ WalletDB.prototype.unwriteBlock = function unwriteBlock(block, callback) { var batch = this.db.batch(); var prev = new WalletBlock(block.prevBlock, block.height - 1); - batch.put('R', prev.toTip()); - batch.del('b/' + block.hash); + batch.put(layout.R, prev.toTip()); + batch.del(layout.b(block.hash)); batch.write(callback); }; @@ -1202,7 +1238,7 @@ WalletDB.prototype.unwriteBlock = function unwriteBlock(block, callback) { */ WalletDB.prototype.getBlock = function getBlock(hash, callback) { - this.db.fetch('b/' + hash, function(err, data) { + this.db.fetch(layout.b(hash), function(err, data) { return WalletBlock.fromRaw(hash, data); }, callback); }; @@ -1214,7 +1250,7 @@ WalletDB.prototype.getBlock = function getBlock(hash, callback) { */ WalletDB.prototype.getWalletsByTX = function getWalletsByTX(hash, callback) { - this.db.fetch('t/' + hash, parseWallets, callback); + this.db.fetch(layout.e(hash), parseWallets, callback); }; /** diff --git a/test/wallet-test.js b/test/wallet-test.js index 8edd7004..36f60e02 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1004,7 +1004,7 @@ describe('Wallet', function() { it('should cleanup', function(cb) { walletdb.dump(function(err, records) { assert.ifError(err); - // utils.log(JSON.stringify(Object.keys(records), null, 2)); + //utils.log(JSON.stringify(Object.keys(records), null, 2)); constants.tx.COINBASE_MATURITY = 100; cb(); });