From 703c9aec3cf42ecb6809b9ede2158f4c90599ce2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 1 May 2016 20:45:36 -0700 Subject: [PATCH] refactor database layout. --- lib/bcoin.js | 21 +++- lib/bcoin/chaindb.js | 121 ++++++++++++----------- lib/bcoin/mempool.js | 69 +++++++------- lib/bcoin/txdb.js | 216 ++++++++++++++++++++---------------------- lib/bcoin/walletdb.js | 25 +++-- test/mempool-test.js | 6 +- test/wallet-test.js | 12 +-- 7 files changed, 250 insertions(+), 220 deletions(-) diff --git a/lib/bcoin.js b/lib/bcoin.js index 837c3aa4..e027b34d 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -8,13 +8,30 @@ */ var Environment = require('./bcoin/env'); +var networks = {}; /** - * Create a new Environment. + * Create a new Environment. Note that this will + * be cached by network. Calling `bcoin('main')` + * twice will return the same environment. * @param {Object} options - See {@link Environment}. * @returns {Environment} */ module.exports = function BCoin(options) { - return new Environment(options); + var network = 'main'; + + if (options) { + if (options.network) + network = options.network; + else if (typeof options === 'string') + network = options; + } + + if (!networks[network]) + networks[network] = new Environment(options); + + return networks[network]; }; + +module.exports.env = Environment; diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index fe7617f1..33b82e73 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -7,6 +7,21 @@ module.exports = function(bcoin) { +/* + * Database Layout: + * R -> tip hash + * e/[hash] -> entry + * h/[hash] -> height + * H/[height] -> hash + * n/[hash] -> next hash + * b/[hash] -> block + * t/[hash] -> extended tx + * c/[hash]/[index] -> coin + * u/[hash] -> undo coins + * T/[address]/[hash] -> dummy (tx by address) + * C/[address]/[hash] -> dummy (coin by address) + */ + var EventEmitter = require('events').EventEmitter; var network = bcoin.protocol.network; var utils = require('./utils'); @@ -110,7 +125,7 @@ ChainDB.prototype._init = function _init() { self.emit('open'); } - self.db.get('c/b/' + network.genesis.hash, function(err, exists) { + self.db.get('h/' + network.genesis.hash, function(err, exists) { if (err && err.type !== 'NotFoundError') return self.emit('error', err); @@ -225,7 +240,7 @@ ChainDB.prototype.getHeight = function getHeight(hash, callback) { if (this.cacheHash.has(hash)) return callback(null, this.cacheHash.get(hash).height); - this.db.get('c/b/' + hash, function(err, height) { + this.db.get('h/' + hash, function(err, height) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -255,7 +270,7 @@ ChainDB.prototype.getHash = function getHash(height, callback) { if (this.cacheHeight.has(height)) return callback(null, this.cacheHeight.get(height).hash); - this.db.get('c/h/' + pad32(height), function(err, hash) { + this.db.get('H/' + pad32(height), function(err, hash) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -389,7 +404,7 @@ ChainDB.prototype._getEntry = function _getEntry(hash, callback) { if (self.cacheHash.has(hash)) return callback(null, self.cacheHash.get(hash)); - return self.db.get('c/c/' + hash, function(err, data) { + return self.db.get('e/' + hash, function(err, data) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -455,8 +470,8 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) { height = new Buffer(4); utils.writeU32(height, entry.height, 0); - batch.put('c/b/' + entry.hash, height); - batch.put('c/c/' + entry.hash, entry.toRaw()); + batch.put('h/' + entry.hash, height); + batch.put('e/' + entry.hash, entry.toRaw()); this.cacheHash.set(entry.hash, entry); @@ -470,9 +485,9 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) { this.cacheHeight.set(entry.height, entry); - batch.put('c/n/' + entry.prevBlock, hash); - batch.put('c/h/' + pad32(entry.height), hash); - batch.put('c/t', hash); + batch.put('n/' + entry.prevBlock, hash); + batch.put('H/' + pad32(entry.height), hash); + batch.put('R', hash); this.emit('add entry', entry); @@ -490,7 +505,7 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) { ChainDB.prototype.getTip = function getTip(callback) { var self = this; - return this.db.get('c/t', function(err, hash) { + return this.db.get('R', function(err, hash) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -512,9 +527,9 @@ ChainDB.prototype.connect = function connect(entry, block, callback) { var batch = this.db.batch(); var hash = new Buffer(entry.hash, 'hex'); - batch.put('c/n/' + entry.prevBlock, hash); - batch.put('c/h/' + pad32(entry.height), hash); - batch.put('c/t', hash); + batch.put('n/' + entry.prevBlock, hash); + batch.put('H/' + pad32(entry.height), hash); + batch.put('R', hash); this.cacheHash.set(entry.hash, entry); this.cacheHeight.set(entry.height, entry); @@ -552,9 +567,9 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) { batch = self.db.batch(); - batch.del('c/n/' + entry.prevBlock); - batch.del('c/h/' + pad32(entry.height)); - batch.put('c/t', new Buffer(entry.prevBlock, 'hex')); + batch.del('n/' + entry.prevBlock); + batch.del('H/' + pad32(entry.height)); + batch.put('R', new Buffer(entry.prevBlock, 'hex')); self.cacheHeight.remove(entry.height); @@ -586,7 +601,7 @@ ChainDB.prototype._ensureEntry = function _ensureEntry(block, callback) { */ ChainDB.prototype.getNextHash = function getNextHash(hash, callback) { - return this.db.get('c/n/' + hash, function(err, nextHash) { + return this.db.get('n/' + hash, function(err, nextHash) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -665,14 +680,14 @@ ChainDB.prototype.reset = function reset(block, callback) { batch = self.db.batch(); if (tip.hash === entry.hash) { - batch.put('c/t', new Buffer(tip.hash, 'hex')); + batch.put('R', new Buffer(tip.hash, 'hex')); return batch.write(callback); } - batch.del('c/h/' + pad32(tip.height)); - batch.del('c/b/' + tip.hash); - batch.del('c/c/' + tip.hash); - batch.del('c/n/' + tip.prevBlock); + batch.del('H/' + pad32(tip.height)); + batch.del('h/' + tip.hash); + batch.del('e/' + tip.hash); + batch.del('n/' + tip.prevBlock); self.emit('remove entry', tip); @@ -725,7 +740,7 @@ ChainDB.prototype.saveBlock = function saveBlock(block, batch, connect, callback if (this.options.spv) return utils.nextTick(callback); - batch.put('b/b/' + block.hash('hex'), block.render()); + batch.put('b/' + block.hash('hex'), block.render()); if (!connect) return utils.nextTick(callback); @@ -755,7 +770,7 @@ ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) { if (!block) return callback(); - batch.del('b/b/' + block.hash('hex')); + batch.del('b/' + block.hash('hex')); self.disconnectBlock(block, batch, callback); }); @@ -794,12 +809,12 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { hash = tx.hash('hex'); if (self.options.indexTX) { - batch.put('t/t/' + hash, tx.toExtended()); + batch.put('t/' + hash, tx.toExtended()); if (self.options.indexAddress) { addresses = tx.getAddresses(); for (j = 0; j < addresses.length; j++) { address = addresses[j]; - batch.put('t/a/' + address + '/' + hash, DUMMY); + batch.put('T/' + address + '/' + hash, DUMMY); } } } @@ -816,10 +831,10 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { if (self.options.indexAddress) { address = input.getAddress(); if (address) - batch.del('u/a/' + address + '/' + key); + batch.del('C/' + address + '/' + key); } - batch.del('u/t/' + key); + batch.del('c/' + key); Framer.coin(input.coin, false, undo); @@ -834,17 +849,17 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) { if (self.options.indexAddress) { address = output.getAddress(); if (address) - batch.put('u/a/' + address + '/' + key, DUMMY); + batch.put('C/' + address + '/' + key, DUMMY); } - batch.put('u/t/' + key, coin.toRaw()); + batch.put('c/' + key, coin.toRaw()); self.coinCache.set(key, coin); } } if (undo.written > 0) - batch.put('b/u/' + block.hash('hex'), undo.render()); + batch.put('u/' + block.hash('hex'), undo.render()); self.emit('add block', block); @@ -882,12 +897,12 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb hash = tx.hash('hex'); if (self.options.indexTX) { - batch.del('t/t/' + hash); + batch.del('t/' + hash); if (self.options.indexAddress) { addresses = tx.getAddresses(); for (j = 0; j < addresses.length; j++) { address = addresses[j]; - batch.del('t/a/' + address + '/' + hash); + batch.del('T/' + address + '/' + hash); } } } @@ -904,10 +919,10 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb if (self.options.indexAddress) { address = input.getAddress(); if (address) - batch.put('u/a/' + address + '/' + key, DUMMY); + batch.put('C/' + address + '/' + key, DUMMY); } - batch.put('u/t/' + key, input.coin.toRaw()); + batch.put('c/' + key, input.coin.toRaw()); self.coinCache.set(key, input.coin); } @@ -919,16 +934,16 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb if (self.options.indexAddress) { address = output.getAddress(); if (address) - batch.del('u/a/' + address + '/' + key); + batch.del('C/' + address + '/' + key); } - batch.del('u/t/' + key); + batch.del('c/' + key); self.coinCache.remove(key); } } - batch.del('b/u/' + block.hash('hex')); + batch.del('u/' + block.hash('hex')); self.emit('remove block', block); @@ -1038,8 +1053,8 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call utils.forEachSerial(addresses, function(address, done) { var iter = self.db.iterator({ - gte: 'u/a/' + address + '/', - lte: 'u/a/' + address + '/~', + gte: 'C/' + address + '/', + lte: 'C/' + address + '/~', keys: true, values: true, fillCache: true, @@ -1061,8 +1076,8 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call return iter.end(done); parts = key.split('/'); - hash = parts[3]; - index = +parts[4]; + hash = parts[2]; + index = +parts[3]; ids.push([hash, index]); @@ -1110,7 +1125,7 @@ ChainDB.prototype.getCoin = function getCoin(hash, index, callback) { if (coin) return utils.asyncify(callback)(null, coin); - this.db.get('u/t/' + key, function(err, data) { + this.db.get('c/' + key, function(err, data) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -1150,8 +1165,8 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback) utils.forEachSerial(addresses, function(address, done) { var iter = self.db.iterator({ - gte: 't/a/' + address + '/', - lte: 't/a/' + address + '/~', + gte: 'T/' + address + '/', + lte: 'T/' + address + '/~', keys: true, values: true, fillCache: true, @@ -1172,7 +1187,7 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback) if (key === undefined) return iter.end(done); - hash = key.split('/')[3]; + hash = key.split('/')[2]; if (addresses.length > 1) { if (have[hash]) @@ -1217,7 +1232,7 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback) ChainDB.prototype.getTX = function getTX(hash, callback) { var self = this; - var key = 't/t/' + hash; + var key = 't/' + hash; var tx; this.db.get(key, function(err, data) { @@ -1419,7 +1434,7 @@ ChainDB.prototype.fillHistoryBlock = function fillHistoryBlock(block, callback) ChainDB.prototype.getUndoCoins = function getUndoCoins(hash, callback) { var coins, p, coin, i, tx; - return this.db.get('b/u/' + hash, function(err, data) { + return this.db.get('u/' + hash, function(err, data) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -1493,7 +1508,7 @@ ChainDB.prototype.getBlock = function getBlock(hash, callback) { if (!hash) return callback(); - key = 'b/b/' + hash; + key = 'b/' + hash; self.db.get(key, function(err, data) { if (err && err.type !== 'NotFoundError') @@ -1547,8 +1562,8 @@ ChainDB.prototype.isSpentTX = function isSpentTX(hash, callback) { hash = hash.hash('hex'); iter = this.db.iterator({ - gte: 'u/t/' + hash, - lte: 'u/t/' + hash + '~', + gte: 'c/' + hash, + lte: 'c/' + hash + '~', keys: true, values: false, fillCache: false, @@ -1598,8 +1613,8 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) { hash = hash.toString('hex'); batch.del(key); - batch.del('b/b/' + hash); - batch.del('b/u/' + hash); + batch.del('b/' + hash); + batch.del('u/' + hash); return callback(); }); diff --git a/lib/bcoin/mempool.js b/lib/bcoin/mempool.js index b3c0ef43..9e427e89 100644 --- a/lib/bcoin/mempool.js +++ b/lib/bcoin/mempool.js @@ -7,6 +7,13 @@ module.exports = function(bcoin) { +/* + * Database Layout: + * (inherits all from txdb) + * o/[hash] -> orphan/waiting hashes + * O/[hash] -> orphan tx + */ + var EventEmitter = require('events').EventEmitter; var bn = require('bn.js'); var constants = bcoin.protocol.constants; @@ -127,7 +134,7 @@ Mempool.prototype._init = function _init() { self.db = bcoin.ldb(options); // Use the txdb object for its get methods. - self.tx = new bcoin.txdb('m', self.db); + self.tx = new bcoin.txdb(self.db); self.db.open(function(err) { if (err) { @@ -322,7 +329,7 @@ Mempool.prototype.purgeOrphans = function purgeOrphans(callback) { callback = utils.ensure(callback); - utils.forEachSerial(['m/D', 'm/d'], function(type, callback) { + utils.forEachSerial(['O', 'o'], function(type, callback) { var iter = self.db.iterator({ gte: type, lte: type + '~', @@ -871,7 +878,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) { p.writeHash(hash); - batch.put('m/d/' + prev, p.render()); + batch.put('o/' + prev, p.render()); next(); }); @@ -881,7 +888,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) { self.orphans++; - batch.put('m/D/' + hash, tx.toExtended(true)); + batch.put('O/' + hash, tx.toExtended(true)); if (self.orphans > constants.mempool.MAX_ORPHAN_TX) { return self.purgeOrphans(function(err) { @@ -924,7 +931,7 @@ Mempool.prototype.getWaiting = function getWaiting(hash, callback) { var hashes = []; var p; - this.db.get('m/d/' + hash, function(err, buf) { + this.db.get('o/' + hash, function(err, buf) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -953,7 +960,7 @@ Mempool.prototype.getWaiting = function getWaiting(hash, callback) { Mempool.prototype.getOrphan = function getOrphan(orphanHash, callback) { var self = this; - this.db.get('m/D/' + orphanHash, function(err, orphan) { + this.db.get('O/' + orphanHash, function(err, orphan) { if (err && err.type !== 'NotFoundError') return next(err); @@ -1015,12 +1022,12 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force) if (orphan.hasCoins()) { self.orphans--; - batch.del('m/D/' + orphanHash); + batch.del('O/' + orphanHash); resolved.push(orphan); return next(); } - batch.put('m/D/' + orphanHash, orphan.toExtended(true)); + batch.put('O/' + orphanHash, orphan.toExtended(true)); next(); }); }, function(err) { @@ -1034,7 +1041,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force) return callback(null, resolved); } - batch.del('m/d/' + hash); + batch.del('o/' + hash); return batch.write(done); }); @@ -1069,7 +1076,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) { hash = tx.hash('hex'); prevout = tx.getPrevout(); - batch.del('m/D/' + hash); + batch.del('O/' + hash); utils.forEach(prevout, function(prev, next) { var i, p; @@ -1085,7 +1092,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) { hashes.splice(i, 1); if (hashes.length === 0) { - batch.del('m/d/' + prev); + batch.del('o/' + prev); return next(); } @@ -1094,7 +1101,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) { for (i = 0; i < hashes.length; i++) p.writeHash(hashes[i]); - batch.put('m/d/' + prev, p.render()); + batch.put('o/' + prev, p.render()); next(); }); @@ -1353,7 +1360,6 @@ Mempool.prototype.getConfidence = function getConfidence(hash, callback) { Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { var self = this; - var prefix = 'm/'; var hash = tx.hash('hex'); var i, addresses, address, input, output, key, coin, batch; @@ -1365,13 +1371,13 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { batch = this.db.batch(); - batch.put(prefix + 't/t/' + hash, tx.toExtended()); - batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('t/' + hash, tx.toExtended()); + batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); addresses = tx.getAddresses(); for (i = 0; i < addresses.length; i++) - batch.put(prefix + 't/a/' + addresses[i] + '/' + hash, DUMMY); + batch.put('T/' + addresses[i] + '/' + hash, DUMMY); for (i = 0; i < tx.inputs.length; i++) { input = tx.inputs[i]; @@ -1384,11 +1390,11 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { address = input.getAddress(); - batch.del(prefix + 'u/t/' + key); - batch.put(prefix + 's/t/' + key, tx.hash()); + batch.del('c/' + key); + batch.put('s/' + key, tx.hash()); if (address) - batch.del(prefix + 'u/a/' + address + '/' + key); + batch.del('C/' + address + '/' + key); } for (i = 0; i < tx.outputs.length; i++) { @@ -1397,10 +1403,10 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { address = output.getAddress(); coin = bcoin.coin(tx, i).toRaw(); - batch.put(prefix + 'u/t/' + key, coin); + batch.put('c/' + key, coin); if (address) - batch.put(prefix + 'u/a/' + address + '/' + key, DUMMY); + batch.put('C/' + address + '/' + key, DUMMY); } return batch.write(callback); @@ -1416,7 +1422,6 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, force) { var self = this; - var prefix = 'm/'; var batch, i, addresses, output; var unlock = this.writeLock.lock(removeUnchecked, [hash, callback], force); @@ -1437,13 +1442,13 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo batch = self.db.batch(); - batch.del(prefix + 't/t/' + hash); - batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash); + batch.del('t/' + hash); + batch.del('m/' + pad32(tx.ps) + '/' + hash); addresses = tx.getAddresses(); for (i = 0; i < addresses.length; i++) - batch.del(prefix + 't/a/' + addresses[i] + '/' + hash); + batch.del('T/' + addresses[i] + '/' + hash); utils.forEachSerial(tx.inputs, function(input, next) { var key = input.prevout.hash + '/' + input.prevout.index; @@ -1457,20 +1462,20 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo address = input.getAddress(); - batch.del(prefix + 's/t/' + key); + batch.del('s/' + key); self.hasTX(input.prevout.hash, function(err, result) { if (err) return next(err); if (result) { - batch.put(prefix + 'u/t/' + key, input.coin.toRaw()); + batch.put('c/' + key, input.coin.toRaw()); if (address) - batch.put(prefix + 'u/a/' + address + '/' + key, DUMMY); + batch.put('C/' + address + '/' + key, DUMMY); } else { - batch.del(prefix + 'u/t/' + key); + batch.del('c/' + key); if (address) - batch.del(prefix + 'u/a/' + address + '/' + key); + batch.del('C/' + address + '/' + key); } next(); @@ -1484,10 +1489,10 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo key = hash + '/' + i; address = output.getAddress(); - batch.del(prefix + 'u/t/' + key); + batch.del('c/' + key); if (address) - batch.del(prefix + 'u/a/' + address + '/' + key); + batch.del('C/' + address + '/' + key); } return batch.write(callback); diff --git a/lib/bcoin/txdb.js b/lib/bcoin/txdb.js index a3507711..734ae0be 100644 --- a/lib/bcoin/txdb.js +++ b/lib/bcoin/txdb.js @@ -7,6 +7,22 @@ module.exports = function(bcoin) { +/* + * Database Layout: + * t/[hash] -> extended tx + * c/[hash]/[index] -> coin + * s/[hash]/[index] -> spent by hash + * o/[hash]/[index] -> orphan inputs + * p/[hash] -> dummy (pending flag) + * m/[time]/[hash] -> dummy (tx by time) + * h/[height]/[hash] -> dummy (tx by height) + * T/[id]/[hash] -> dummy (tx by wallet id) + * P/[id]/[hash] -> dummy (pending tx by wallet id) + * M/[id]/[time]/[hash] -> dummy (tx by time + id) + * H/[id]/[height]/[hash] -> dummy (tx by height + id) + * C/[id]/[hash]/[index] -> dummy (coin by address) + */ + var bn = require('bn.js'); var utils = require('./utils'); var assert = bcoin.utils.assert; @@ -20,8 +36,6 @@ var BufferWriter = require('./writer'); * TXDB * @exports TXDB * @constructor - * @param {String} prefix - The prefix for - * the database (`txdb/` by default). * @param {LowlevelUp} db * @param {Object?} options * @param {Boolean?} options.mapAddress - Map addresses to IDs. @@ -29,12 +43,11 @@ var BufferWriter = require('./writer'); * @param {Boolean?} options.indexExtra - Index timestamps, heights, etc. * @param {Boolean?} options.verify - Verify transactions as they * come in (note that this will not happen on the worker pool). - * @property {String} prefix */ -function TXDB(prefix, db, options) { +function TXDB(db, options) { if (!(this instanceof TXDB)) - return new TXDB(prefix, db, options); + return new TXDB(db, options); EventEmitter.call(this); @@ -42,7 +55,6 @@ function TXDB(prefix, db, options) { options = {}; this.db = db; - this.prefix = prefix || 'txdb'; this.options = options; this.busy = false; this.jobs = []; @@ -121,7 +133,6 @@ TXDB.prototype.getMap = function getMap(tx, callback) { TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { var self = this; - var prefix = this.prefix + '/'; var table = {}; var iter; @@ -145,8 +156,8 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { } iter = this.db.iterator({ - gte: prefix + 'a/' + address, - lte: prefix + 'a/' + address + '~', + gte: 'W/' + address, + lte: 'W/' + address + '~', keys: true, values: false, fillCache: false, @@ -173,7 +184,7 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { }); } - key = key.split('/')[3]; + key = key.split('/')[2]; table[address].push(key); next(); @@ -192,10 +203,9 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { */ TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) { - var prefix = this.prefix + '/'; var p; - this.db.get(prefix + 'o/' + key, function(err, buf) { + this.db.get('o/' + key, function(err, buf) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -220,10 +230,9 @@ TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) { TXDB.prototype._getOrphans = function _getOrphans(key, callback) { var self = this; - var prefix = this.prefix + '/'; var p, orphans; - this.db.get(prefix + 'o/' + key, function(err, buf) { + this.db.get('o/' + key, function(err, buf) { if (err && err.type !== 'NotFoundError') return callback(err); @@ -297,7 +306,6 @@ TXDB.prototype.add = function add(tx, callback, force) { // receiving txs out of order. TXDB.prototype._add = function add(tx, map, callback, force) { var self = this; - var prefix = this.prefix + '/'; var hash = tx.hash('hex'); var updated = false; var batch; @@ -324,30 +332,27 @@ TXDB.prototype._add = function add(tx, map, callback, force) { batch = self.db.batch(); - batch.put(prefix + 't/t/' + hash, tx.toExtended()); + batch.put('t/' + hash, tx.toExtended()); if (self.options.indexExtra) { if (tx.ts === 0) { assert(tx.ps > 0); - batch.put(prefix + 't/p/t/' + hash, DUMMY); - batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('p/' + hash, DUMMY); + batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); } else { - batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY); } if (self.options.indexAddress) { map.all.forEach(function(id) { - batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY); + batch.put('T/' + id + '/' + hash, DUMMY); if (tx.ts === 0) { - batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); - batch.put( - prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('P/' + id + '/' + hash, DUMMY); + batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); } else { - batch.put( - prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.put( - prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); } }); } @@ -388,12 +393,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (self.options.indexAddress && address) { map.table[address].forEach(function(id) { - batch.del(prefix + 'u/a/' + id + '/' + key); + batch.del('C/' + id + '/' + key); }); } - batch.del(prefix + 'u/t/' + key); - batch.put(prefix + 's/t/' + key, tx.hash()); + batch.del('c/' + key); + batch.put('s/' + key, tx.hash()); return next(); } @@ -442,7 +447,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (err) return next(err); - batch.put(prefix + 'o/' + key, orphans); + batch.put('o/' + key, orphans); return next(); }); @@ -513,7 +518,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (!some) orphans = null; - self.db.del(prefix + 'o/' + key, finish); + self.db.del('o/' + key, finish); }); function finish(err) { @@ -523,11 +528,11 @@ TXDB.prototype._add = function add(tx, map, callback, force) { if (!orphans) { if (self.options.indexAddress && address) { map.table[address].forEach(function(id) { - batch.put(prefix + 'u/a/' + id + '/' + key, DUMMY); + batch.put('C/' + id + '/' + key, DUMMY); }); } - batch.put(prefix + 'u/t/' + key, coin.toRaw()); + batch.put('c/' + key, coin.toRaw()); updated = true; } @@ -639,8 +644,7 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) { TXDB.prototype.isSpent = function isSpent(hash, index, callback) { var self = this; - var prefix = this.prefix + '/'; - var key = prefix + 's/t/' + hash + '/' + index; + var key = 's/' + hash + '/' + index; return this.db.get(key, function(err, hash) { if (err && err.type !== 'NotFoundError') @@ -665,7 +669,6 @@ TXDB.prototype.isSpent = function isSpent(hash, index, callback) { TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { var self = this; - var prefix = this.prefix + '/'; var hash = tx.hash('hex'); var batch; @@ -698,20 +701,20 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { assert(tx.height >= 0); assert(existing.ps > 0); - batch.put(prefix + 't/t/' + hash, tx.toExtended()); + batch.put('t/' + hash, tx.toExtended()); if (self.options.indexExtra) { - batch.del(prefix + 't/p/t/' + hash); - batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); - batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.del('p/' + hash); + batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del('m/' + pad32(existing.ps) + '/' + hash); + batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY); if (self.options.indexAddress) { map.all.forEach(function(id) { - batch.del(prefix + 't/p/a/' + id + '/' + hash); - batch.put(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); - batch.del(prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); - batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); + batch.del('P/' + id + '/' + hash); + batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); + batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash); + batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); }); } } @@ -734,7 +737,7 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) { coin.height = tx.height; - batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); + batch.put('c/' + hash + '/' + i, coin.toRaw()); next(); }); @@ -836,7 +839,6 @@ TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) { TXDB.prototype._remove = function remove(tx, map, callback, force) { var self = this; - var prefix = this.prefix + '/'; var hash = tx.hash('hex'); var batch; @@ -848,26 +850,26 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { batch = this.db.batch(); - batch.del(prefix + 't/t/' + hash); + batch.del('t/' + hash); if (self.options.indexExtra) { if (tx.ts === 0) { - batch.del(prefix + 't/p/t/' + hash); - batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash); + batch.del('p/' + hash); + batch.del('m/' + pad32(tx.ps) + '/' + hash); } else { - batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash); - batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash); + batch.del('h/' + pad32(tx.height) + '/' + hash); + batch.del('m/' + pad32(tx.ts) + '/' + hash); } if (self.options.indexAddress) { map.all.forEach(function(id) { - batch.del(prefix + 't/a/' + id + '/' + hash); + batch.del('T/' + id + '/' + hash); if (tx.ts === 0) { - batch.del(prefix + 't/p/a/' + id + '/' + hash); - batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash); + batch.del('P/' + id + '/' + hash); + batch.del('M/' + id + '/' + pad32(tx.ps) + '/' + hash); } else { - batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash); - batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash); + batch.del('H/' + id + '/' + pad32(tx.height) + '/' + hash); + batch.del('M/' + id + '/' + pad32(tx.ts) + '/' + hash); } }); } @@ -894,13 +896,13 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { if (self.options.indexAddress && address) { map.table[address].forEach(function(id) { - batch.put(prefix + 'u/a/' + id + '/' + key, DUMMY); + batch.put('C/' + id + '/' + key, DUMMY); }); } - batch.put(prefix + 'u/t/' + key, input.coin.toRaw()); - batch.del(prefix + 's/t/' + key); - batch.del(prefix + 'o/' + key); + batch.put('c/' + key, input.coin.toRaw()); + batch.del('s/' + key); + batch.del('o/' + key); }); tx.outputs.forEach(function(output, i) { @@ -914,11 +916,11 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) { if (self.options.indexAddress && address) { map.table[address].forEach(function(id) { - batch.del(prefix + 'u/a/' + id + '/' + key); + batch.del('C/' + id + '/' + key); }); } - batch.del(prefix + 'u/t/' + key); + batch.del('c/' + key); }); batch.write(function(err) { @@ -984,7 +986,6 @@ TXDB.prototype.unconfirm = function unconfirm(hash, callback, force) { TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { var self = this; - var prefix = this.prefix + '/'; var hash, batch, height, ts; var unlock = this._lock(unconfirm, [tx, map, callback], force); @@ -1007,20 +1008,20 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { tx.index = -1; tx.block = null; - batch.put(prefix + 't/t/' + hash, tx.toExtended()); + batch.put('t/' + hash, tx.toExtended()); if (self.options.indexExtra) { - batch.put(prefix + 't/p/t/' + hash, DUMMY); - batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash); - batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash); - batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('p/' + hash, DUMMY); + batch.del('h/' + pad32(height) + '/' + hash); + batch.del('m/' + pad32(ts) + '/' + hash); + batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY); if (self.options.indexAddress) { map.all.forEach(function(id) { - batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); - batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash); - batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash); - batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); + batch.put('P/' + id + '/' + hash, DUMMY); + batch.del('H/' + id + '/' + pad32(height) + '/' + hash); + batch.del('M/' + id + '/' + pad32(ts) + '/' + hash); + batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); }); } } @@ -1035,7 +1036,7 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { coin.height = tx.height; - batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); + batch.put('c/' + hash + '/' + i, coin.toRaw()); next(); }); @@ -1062,7 +1063,6 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) { TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { var self = this; - var prefix = this.prefix + '/'; var txs = []; var iter; @@ -1094,8 +1094,8 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { } iter = this.db.iterator({ - gte: address ? prefix + 't/a/' + address : prefix + 't/t', - lte: address ? prefix + 't/a/' + address + '~' : prefix + 't/t~', + gte: address ? 'T/' + address : 't', + lte: address ? 'T/' + address + '~' : 't~', keys: true, values: false, fillCache: false, @@ -1119,9 +1119,9 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { } if (address) - txs.push(key.split('/')[4]); + txs.push(key.split('/')[2]); else - txs.push(key.split('/')[3]); + txs.push(key.split('/')[1]); next(); }); @@ -1136,7 +1136,6 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, callback) { var self = this; - var prefix = this.prefix + '/'; var txs = []; var iter; @@ -1169,8 +1168,8 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal } iter = this.db.iterator({ - gte: address ? prefix + 't/p/a/' + address : prefix + 't/p/t', - lte: address ? prefix + 't/p/a/' + address + '~' : prefix + 't/p/t~', + gte: address ? 'P/' + address : 'p', + lte: address ? 'P/' + address + '~' : 'p~', keys: true, values: false, fillCache: false, @@ -1194,9 +1193,9 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal } if (address) - txs.push(key.split('/')[5]); + txs.push(key.split('/')[2]); else - txs.push(key.split('/')[4]); + txs.push(key.split('/')[1]); next(); }); @@ -1211,7 +1210,6 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { var self = this; - var prefix = this.prefix + '/'; var coins = []; var iter; @@ -1242,11 +1240,11 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { iter = this.db.iterator({ gte: address - ? prefix + 'u/a/' + address - : prefix + 'u/t', + ? 'C/' + address + : 'c', lte: address - ? prefix + 'u/a/' + address + '~' - : prefix + 'u/t~', + ? 'C/' + address + '~' + : 'c~', keys: true, values: false, fillCache: false, @@ -1272,9 +1270,9 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { key = key.split('/'); if (address) - coins.push([key[4], +key[5]]); + coins.push([key[2], +key[3]]); else - coins.push([key[3], +key[4]]); + coins.push([key[1], +key[2]]); next(); }); @@ -1293,7 +1291,6 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { */ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, options, callback) { - var prefix = this.prefix + '/'; var txs = []; var iter; @@ -1310,11 +1307,11 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, opt iter = this.db.iterator({ gte: address - ? prefix + 't/h/a/' + address + '/' + pad32(options.start) + '/' - : prefix + 't/h/h/' + pad32(options.start) + '/', + ? 'H/' + address + '/' + pad32(options.start) + '/' + : 'h/' + pad32(options.start) + '/', lte: address - ? prefix + 't/h/a/' + address + '/' + pad32(options.end) + '/~' - : prefix + 't/h/h/' + pad32(options.end) + '/~', + ? 'H/' + address + '/' + pad32(options.end) + '/~' + : 'h/' + pad32(options.end) + '/~', keys: true, values: false, fillCache: false, @@ -1340,9 +1337,9 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, opt } if (address) - txs.push(key.split('/')[6]); + txs.push(key.split('/')[3]); else - txs.push(key.split('/')[5]); + txs.push(key.split('/')[2]); next(); }); @@ -1371,7 +1368,6 @@ TXDB.prototype.getHeightHashes = function getHeightHashes(height, callback) { */ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callback) { - var prefix = this.prefix + '/'; var txs = []; var iter; @@ -1384,11 +1380,11 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callba iter = this.db.iterator({ gte: address - ? prefix + 't/s/a/' + address + '/' + pad32(options.start) + '/' - : prefix + 't/s/s/' + pad32(options.start) + '/', + ? 'M/' + address + '/' + pad32(options.start) + '/' + : 'm/' + pad32(options.start) + '/', lte: address - ? prefix + 't/s/a/' + address + '/' + pad32(options.end) + '/~' - : prefix + 't/s/s/' + pad32(options.end) + '/~', + ? 'M/' + address + '/' + pad32(options.end) + '/~' + : 'm/' + pad32(options.end) + '/~', keys: true, values: false, fillCache: false, @@ -1414,9 +1410,9 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callba } if (address) - txs.push(key.split('/')[6]); + txs.push(key.split('/')[3]); else - txs.push(key.split('/')[5]); + txs.push(key.split('/')[2]); next(); }); @@ -1740,8 +1736,7 @@ TXDB.prototype.fillCoins = function fillCoins(tx, callback) { */ TXDB.prototype.getTX = function getTX(hash, callback) { - var prefix = this.prefix + '/'; - var key = prefix + 't/t/' + hash; + var key = 't/' + hash; this.db.get(key, function(err, tx) { if (err && err.type !== 'NotFoundError') @@ -1783,8 +1778,7 @@ TXDB.prototype.hasTX = function hasTX(hash, callback) { */ TXDB.prototype.getCoin = function getCoin(hash, index, callback) { - var prefix = this.prefix + '/'; - var key = prefix + 'u/t/' + hash + '/' + index; + var key = 'c/' + hash + '/' + index; this.db.get(key, function(err, coin) { if (err && err.type !== 'NotFoundError') diff --git a/lib/bcoin/walletdb.js b/lib/bcoin/walletdb.js index 855f2340..1a21a503 100644 --- a/lib/bcoin/walletdb.js +++ b/lib/bcoin/walletdb.js @@ -7,6 +7,13 @@ module.exports = function(bcoin) { +/* + * Database Layout: + * (inherits all from txdb) + * W/[address]/[id] -> dummy (map address to id) + * w/[id] -> wallet + */ + var EventEmitter = require('events').EventEmitter; var utils = require('./utils'); var assert = utils.assert; @@ -108,7 +115,7 @@ WalletDB.prototype._init = function _init() { self.loaded = true; }); - this.tx = new bcoin.txdb('w', this.db, { + this.tx = new bcoin.txdb(this.db, { indexExtra: true, indexAddress: true, mapAddress: true, @@ -366,7 +373,7 @@ WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) { if (json) { batch = self.db.batch(); Object.keys(json.addressMap).forEach(function(address) { - batch.put('w/a/' + address + '/' + json.id, DUMMY); + batch.put('W/' + address + '/' + json.id, DUMMY); }); return batch.write(function(err) { if (err) @@ -402,7 +409,7 @@ WalletDB.prototype.removeJSON = function removeJSON(id, callback) { if (json) { batch = self.db.batch(); Object.keys(json.addressMap).forEach(function(address) { - batch.del('w/a/' + address + '/' + json.id); + batch.del('W/' + address + '/' + json.id); }); return batch.write(function(err) { if (err) @@ -423,7 +430,7 @@ WalletDB.prototype.removeJSON = function removeJSON(id, callback) { */ WalletDB.prototype._getDB = function _getDB(id, callback) { - var key = 'w/w/' + id; + var key = 'w/' + id; callback = utils.ensure(callback); @@ -453,7 +460,7 @@ WalletDB.prototype._getDB = function _getDB(id, callback) { */ WalletDB.prototype._saveDB = function _saveDB(id, json, callback) { - var key = 'w/w/' + id; + var key = 'w/' + id; var data; callback = utils.ensure(callback); @@ -477,7 +484,7 @@ WalletDB.prototype._saveDB = function _saveDB(id, json, callback) { WalletDB.prototype._removeDB = function _removeDB(id, callback) { var self = this; - var key = 'w/w/' + id; + var key = 'w/' + id; callback = utils.ensure(callback); @@ -691,18 +698,18 @@ WalletDB.prototype.update = function update(wallet, address) { batch = this.db.batch(); batch.put( - 'w/a/' + address.getKeyAddress() + '/' + wallet.id, + 'W/' + address.getKeyAddress() + '/' + wallet.id, DUMMY); if (address.type === 'multisig') { batch.put( - 'w/a/' + address.getScriptAddress() + '/' + wallet.id, + 'W/' + address.getScriptAddress() + '/' + wallet.id, DUMMY); } if (address.witness) { batch.put( - 'w/a/' + address.getProgramAddress() + '/' + wallet.id, + 'W/' + address.getProgramAddress() + '/' + wallet.id, DUMMY); } diff --git a/test/mempool-test.js b/test/mempool-test.js index 00df2adb..88797e14 100644 --- a/test/mempool-test.js +++ b/test/mempool-test.js @@ -1,13 +1,13 @@ var bn = require('bn.js'); -var bcoin = require('../')({ db: 'memory' }); +var bcoin = require('../')(); var constants = bcoin.protocol.constants; var utils = bcoin.utils; var assert = require('assert'); var opcodes = constants.opcodes; describe('Mempool', function() { - var chain = new bcoin.chain(); - var mempool = new bcoin.mempool({ chain: chain }); + var chain = new bcoin.chain({ db: 'memory' }); + var mempool = new bcoin.mempool({ chain: chain, db: 'memory' }); mempool.on('error', function() {}); it('should open mempool', function(cb) { diff --git a/test/wallet-test.js b/test/wallet-test.js index 981a8721..5ac9955e 100644 --- a/test/wallet-test.js +++ b/test/wallet-test.js @@ -1,5 +1,5 @@ var bn = require('bn.js'); -var bcoin = require('../')({ db: process.env.BCOIN_TEST_DB || 'memory' }); +var bcoin = require('../').env(); var constants = bcoin.protocol.constants; var utils = bcoin.utils; var assert = require('assert'); @@ -24,7 +24,7 @@ var dummyInput = { }; describe('Wallet', function() { - var wdb = new bcoin.walletdb({ verify: true }); + var wdb = new bcoin.walletdb({ db: 'memory', verify: true }); it('should open walletdb', function(cb) { wdb.open(cb); @@ -554,12 +554,4 @@ describe('Wallet', function() { it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', function(cb) { multisig(true, true, cb); }); - - it('should have gratuitous dump', function(cb) { - bcoin.walletdb().dump(function(err, records) { - assert.ifError(err); - // console.log(records); - setTimeout(cb, 200); - }); - }); });