refactor database layout.

This commit is contained in:
Christopher Jeffrey 2016-05-01 20:45:36 -07:00
parent 0f4a591665
commit 703c9aec3c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
7 changed files with 250 additions and 220 deletions

View File

@ -8,13 +8,30 @@
*/ */
var Environment = require('./bcoin/env'); 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}. * @param {Object} options - See {@link Environment}.
* @returns {Environment} * @returns {Environment}
*/ */
module.exports = function BCoin(options) { 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;

View File

@ -7,6 +7,21 @@
module.exports = function(bcoin) { 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 EventEmitter = require('events').EventEmitter;
var network = bcoin.protocol.network; var network = bcoin.protocol.network;
var utils = require('./utils'); var utils = require('./utils');
@ -110,7 +125,7 @@ ChainDB.prototype._init = function _init() {
self.emit('open'); 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') if (err && err.type !== 'NotFoundError')
return self.emit('error', err); return self.emit('error', err);
@ -225,7 +240,7 @@ ChainDB.prototype.getHeight = function getHeight(hash, callback) {
if (this.cacheHash.has(hash)) if (this.cacheHash.has(hash))
return callback(null, this.cacheHash.get(hash).height); 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -255,7 +270,7 @@ ChainDB.prototype.getHash = function getHash(height, callback) {
if (this.cacheHeight.has(height)) if (this.cacheHeight.has(height))
return callback(null, this.cacheHeight.get(height).hash); 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -389,7 +404,7 @@ ChainDB.prototype._getEntry = function _getEntry(hash, callback) {
if (self.cacheHash.has(hash)) if (self.cacheHash.has(hash))
return callback(null, self.cacheHash.get(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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -455,8 +470,8 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) {
height = new Buffer(4); height = new Buffer(4);
utils.writeU32(height, entry.height, 0); utils.writeU32(height, entry.height, 0);
batch.put('c/b/' + entry.hash, height); batch.put('h/' + entry.hash, height);
batch.put('c/c/' + entry.hash, entry.toRaw()); batch.put('e/' + entry.hash, entry.toRaw());
this.cacheHash.set(entry.hash, entry); 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); this.cacheHeight.set(entry.height, entry);
batch.put('c/n/' + entry.prevBlock, hash); batch.put('n/' + entry.prevBlock, hash);
batch.put('c/h/' + pad32(entry.height), hash); batch.put('H/' + pad32(entry.height), hash);
batch.put('c/t', hash); batch.put('R', hash);
this.emit('add entry', entry); this.emit('add entry', entry);
@ -490,7 +505,7 @@ ChainDB.prototype.save = function save(entry, block, connect, callback) {
ChainDB.prototype.getTip = function getTip(callback) { ChainDB.prototype.getTip = function getTip(callback) {
var self = this; 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -512,9 +527,9 @@ ChainDB.prototype.connect = function connect(entry, block, callback) {
var batch = this.db.batch(); var batch = this.db.batch();
var hash = new Buffer(entry.hash, 'hex'); var hash = new Buffer(entry.hash, 'hex');
batch.put('c/n/' + entry.prevBlock, hash); batch.put('n/' + entry.prevBlock, hash);
batch.put('c/h/' + pad32(entry.height), hash); batch.put('H/' + pad32(entry.height), hash);
batch.put('c/t', hash); batch.put('R', hash);
this.cacheHash.set(entry.hash, entry); this.cacheHash.set(entry.hash, entry);
this.cacheHeight.set(entry.height, entry); this.cacheHeight.set(entry.height, entry);
@ -552,9 +567,9 @@ ChainDB.prototype.disconnect = function disconnect(block, callback) {
batch = self.db.batch(); batch = self.db.batch();
batch.del('c/n/' + entry.prevBlock); batch.del('n/' + entry.prevBlock);
batch.del('c/h/' + pad32(entry.height)); batch.del('H/' + pad32(entry.height));
batch.put('c/t', new Buffer(entry.prevBlock, 'hex')); batch.put('R', new Buffer(entry.prevBlock, 'hex'));
self.cacheHeight.remove(entry.height); self.cacheHeight.remove(entry.height);
@ -586,7 +601,7 @@ ChainDB.prototype._ensureEntry = function _ensureEntry(block, callback) {
*/ */
ChainDB.prototype.getNextHash = function getNextHash(hash, 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -665,14 +680,14 @@ ChainDB.prototype.reset = function reset(block, callback) {
batch = self.db.batch(); batch = self.db.batch();
if (tip.hash === entry.hash) { 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); return batch.write(callback);
} }
batch.del('c/h/' + pad32(tip.height)); batch.del('H/' + pad32(tip.height));
batch.del('c/b/' + tip.hash); batch.del('h/' + tip.hash);
batch.del('c/c/' + tip.hash); batch.del('e/' + tip.hash);
batch.del('c/n/' + tip.prevBlock); batch.del('n/' + tip.prevBlock);
self.emit('remove entry', tip); self.emit('remove entry', tip);
@ -725,7 +740,7 @@ ChainDB.prototype.saveBlock = function saveBlock(block, batch, connect, callback
if (this.options.spv) if (this.options.spv)
return utils.nextTick(callback); return utils.nextTick(callback);
batch.put('b/b/' + block.hash('hex'), block.render()); batch.put('b/' + block.hash('hex'), block.render());
if (!connect) if (!connect)
return utils.nextTick(callback); return utils.nextTick(callback);
@ -755,7 +770,7 @@ ChainDB.prototype.removeBlock = function removeBlock(hash, batch, callback) {
if (!block) if (!block)
return callback(); return callback();
batch.del('b/b/' + block.hash('hex')); batch.del('b/' + block.hash('hex'));
self.disconnectBlock(block, batch, callback); self.disconnectBlock(block, batch, callback);
}); });
@ -794,12 +809,12 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
hash = tx.hash('hex'); hash = tx.hash('hex');
if (self.options.indexTX) { if (self.options.indexTX) {
batch.put('t/t/' + hash, tx.toExtended()); batch.put('t/' + hash, tx.toExtended());
if (self.options.indexAddress) { if (self.options.indexAddress) {
addresses = tx.getAddresses(); addresses = tx.getAddresses();
for (j = 0; j < addresses.length; j++) { for (j = 0; j < addresses.length; j++) {
address = addresses[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) { if (self.options.indexAddress) {
address = input.getAddress(); address = input.getAddress();
if (address) 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); Framer.coin(input.coin, false, undo);
@ -834,17 +849,17 @@ ChainDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
if (self.options.indexAddress) { if (self.options.indexAddress) {
address = output.getAddress(); address = output.getAddress();
if (address) 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); self.coinCache.set(key, coin);
} }
} }
if (undo.written > 0) 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); self.emit('add block', block);
@ -882,12 +897,12 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
hash = tx.hash('hex'); hash = tx.hash('hex');
if (self.options.indexTX) { if (self.options.indexTX) {
batch.del('t/t/' + hash); batch.del('t/' + hash);
if (self.options.indexAddress) { if (self.options.indexAddress) {
addresses = tx.getAddresses(); addresses = tx.getAddresses();
for (j = 0; j < addresses.length; j++) { for (j = 0; j < addresses.length; j++) {
address = addresses[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) { if (self.options.indexAddress) {
address = input.getAddress(); address = input.getAddress();
if (address) 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); self.coinCache.set(key, input.coin);
} }
@ -919,16 +934,16 @@ ChainDB.prototype.disconnectBlock = function disconnectBlock(block, batch, callb
if (self.options.indexAddress) { if (self.options.indexAddress) {
address = output.getAddress(); address = output.getAddress();
if (address) 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); self.coinCache.remove(key);
} }
} }
batch.del('b/u/' + block.hash('hex')); batch.del('u/' + block.hash('hex'));
self.emit('remove block', block); self.emit('remove block', block);
@ -1038,8 +1053,8 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call
utils.forEachSerial(addresses, function(address, done) { utils.forEachSerial(addresses, function(address, done) {
var iter = self.db.iterator({ var iter = self.db.iterator({
gte: 'u/a/' + address + '/', gte: 'C/' + address + '/',
lte: 'u/a/' + address + '/~', lte: 'C/' + address + '/~',
keys: true, keys: true,
values: true, values: true,
fillCache: true, fillCache: true,
@ -1061,8 +1076,8 @@ ChainDB.prototype.getCoinsByAddress = function getCoinsByAddress(addresses, call
return iter.end(done); return iter.end(done);
parts = key.split('/'); parts = key.split('/');
hash = parts[3]; hash = parts[2];
index = +parts[4]; index = +parts[3];
ids.push([hash, index]); ids.push([hash, index]);
@ -1110,7 +1125,7 @@ ChainDB.prototype.getCoin = function getCoin(hash, index, callback) {
if (coin) if (coin)
return utils.asyncify(callback)(null, 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -1150,8 +1165,8 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
utils.forEachSerial(addresses, function(address, done) { utils.forEachSerial(addresses, function(address, done) {
var iter = self.db.iterator({ var iter = self.db.iterator({
gte: 't/a/' + address + '/', gte: 'T/' + address + '/',
lte: 't/a/' + address + '/~', lte: 'T/' + address + '/~',
keys: true, keys: true,
values: true, values: true,
fillCache: true, fillCache: true,
@ -1172,7 +1187,7 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
if (key === undefined) if (key === undefined)
return iter.end(done); return iter.end(done);
hash = key.split('/')[3]; hash = key.split('/')[2];
if (addresses.length > 1) { if (addresses.length > 1) {
if (have[hash]) if (have[hash])
@ -1217,7 +1232,7 @@ ChainDB.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
ChainDB.prototype.getTX = function getTX(hash, callback) { ChainDB.prototype.getTX = function getTX(hash, callback) {
var self = this; var self = this;
var key = 't/t/' + hash; var key = 't/' + hash;
var tx; var tx;
this.db.get(key, function(err, data) { 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) { ChainDB.prototype.getUndoCoins = function getUndoCoins(hash, callback) {
var coins, p, coin, i, tx; 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -1493,7 +1508,7 @@ ChainDB.prototype.getBlock = function getBlock(hash, callback) {
if (!hash) if (!hash)
return callback(); return callback();
key = 'b/b/' + hash; key = 'b/' + hash;
self.db.get(key, function(err, data) { self.db.get(key, function(err, data) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
@ -1547,8 +1562,8 @@ ChainDB.prototype.isSpentTX = function isSpentTX(hash, callback) {
hash = hash.hash('hex'); hash = hash.hash('hex');
iter = this.db.iterator({ iter = this.db.iterator({
gte: 'u/t/' + hash, gte: 'c/' + hash,
lte: 'u/t/' + hash + '~', lte: 'c/' + hash + '~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1598,8 +1613,8 @@ ChainDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
hash = hash.toString('hex'); hash = hash.toString('hex');
batch.del(key); batch.del(key);
batch.del('b/b/' + hash); batch.del('b/' + hash);
batch.del('b/u/' + hash); batch.del('u/' + hash);
return callback(); return callback();
}); });

View File

@ -7,6 +7,13 @@
module.exports = function(bcoin) { 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 EventEmitter = require('events').EventEmitter;
var bn = require('bn.js'); var bn = require('bn.js');
var constants = bcoin.protocol.constants; var constants = bcoin.protocol.constants;
@ -127,7 +134,7 @@ Mempool.prototype._init = function _init() {
self.db = bcoin.ldb(options); self.db = bcoin.ldb(options);
// Use the txdb object for its get methods. // 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) { self.db.open(function(err) {
if (err) { if (err) {
@ -322,7 +329,7 @@ Mempool.prototype.purgeOrphans = function purgeOrphans(callback) {
callback = utils.ensure(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({ var iter = self.db.iterator({
gte: type, gte: type,
lte: type + '~', lte: type + '~',
@ -871,7 +878,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
p.writeHash(hash); p.writeHash(hash);
batch.put('m/d/' + prev, p.render()); batch.put('o/' + prev, p.render());
next(); next();
}); });
@ -881,7 +888,7 @@ Mempool.prototype.storeOrphan = function storeOrphan(tx, callback, force) {
self.orphans++; 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) { if (self.orphans > constants.mempool.MAX_ORPHAN_TX) {
return self.purgeOrphans(function(err) { return self.purgeOrphans(function(err) {
@ -924,7 +931,7 @@ Mempool.prototype.getWaiting = function getWaiting(hash, callback) {
var hashes = []; var hashes = [];
var p; var p;
this.db.get('m/d/' + hash, function(err, buf) { this.db.get('o/' + hash, function(err, buf) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -953,7 +960,7 @@ Mempool.prototype.getWaiting = function getWaiting(hash, callback) {
Mempool.prototype.getOrphan = function getOrphan(orphanHash, callback) { Mempool.prototype.getOrphan = function getOrphan(orphanHash, callback) {
var self = this; 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') if (err && err.type !== 'NotFoundError')
return next(err); return next(err);
@ -1015,12 +1022,12 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
if (orphan.hasCoins()) { if (orphan.hasCoins()) {
self.orphans--; self.orphans--;
batch.del('m/D/' + orphanHash); batch.del('O/' + orphanHash);
resolved.push(orphan); resolved.push(orphan);
return next(); return next();
} }
batch.put('m/D/' + orphanHash, orphan.toExtended(true)); batch.put('O/' + orphanHash, orphan.toExtended(true));
next(); next();
}); });
}, function(err) { }, function(err) {
@ -1034,7 +1041,7 @@ Mempool.prototype.resolveOrphans = function resolveOrphans(tx, callback, force)
return callback(null, resolved); return callback(null, resolved);
} }
batch.del('m/d/' + hash); batch.del('o/' + hash);
return batch.write(done); return batch.write(done);
}); });
@ -1069,7 +1076,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) {
hash = tx.hash('hex'); hash = tx.hash('hex');
prevout = tx.getPrevout(); prevout = tx.getPrevout();
batch.del('m/D/' + hash); batch.del('O/' + hash);
utils.forEach(prevout, function(prev, next) { utils.forEach(prevout, function(prev, next) {
var i, p; var i, p;
@ -1085,7 +1092,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) {
hashes.splice(i, 1); hashes.splice(i, 1);
if (hashes.length === 0) { if (hashes.length === 0) {
batch.del('m/d/' + prev); batch.del('o/' + prev);
return next(); return next();
} }
@ -1094,7 +1101,7 @@ Mempool.prototype.removeOrphan = function removeOrphan(tx, callback) {
for (i = 0; i < hashes.length; i++) for (i = 0; i < hashes.length; i++)
p.writeHash(hashes[i]); p.writeHash(hashes[i]);
batch.put('m/d/' + prev, p.render()); batch.put('o/' + prev, p.render());
next(); next();
}); });
@ -1353,7 +1360,6 @@ Mempool.prototype.getConfidence = function getConfidence(hash, callback) {
Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) { Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) {
var self = this; var self = this;
var prefix = 'm/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var i, addresses, address, input, output, key, coin, batch; 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 = this.db.batch();
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put('t/' + hash, tx.toExtended());
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
addresses = tx.getAddresses(); addresses = tx.getAddresses();
for (i = 0; i < addresses.length; i++) 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++) { for (i = 0; i < tx.inputs.length; i++) {
input = tx.inputs[i]; input = tx.inputs[i];
@ -1384,11 +1390,11 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) {
address = input.getAddress(); address = input.getAddress();
batch.del(prefix + 'u/t/' + key); batch.del('c/' + key);
batch.put(prefix + 's/t/' + key, tx.hash()); batch.put('s/' + key, tx.hash());
if (address) if (address)
batch.del(prefix + 'u/a/' + address + '/' + key); batch.del('C/' + address + '/' + key);
} }
for (i = 0; i < tx.outputs.length; i++) { for (i = 0; i < tx.outputs.length; i++) {
@ -1397,10 +1403,10 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) {
address = output.getAddress(); address = output.getAddress();
coin = bcoin.coin(tx, i).toRaw(); coin = bcoin.coin(tx, i).toRaw();
batch.put(prefix + 'u/t/' + key, coin); batch.put('c/' + key, coin);
if (address) if (address)
batch.put(prefix + 'u/a/' + address + '/' + key, DUMMY); batch.put('C/' + address + '/' + key, DUMMY);
} }
return batch.write(callback); return batch.write(callback);
@ -1416,7 +1422,6 @@ Mempool.prototype._addUnchecked = function addUnchecked(tx, callback, force) {
Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, force) { Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, force) {
var self = this; var self = this;
var prefix = 'm/';
var batch, i, addresses, output; var batch, i, addresses, output;
var unlock = this.writeLock.lock(removeUnchecked, [hash, callback], force); 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 = self.db.batch();
batch.del(prefix + 't/t/' + hash); batch.del('t/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash); batch.del('m/' + pad32(tx.ps) + '/' + hash);
addresses = tx.getAddresses(); addresses = tx.getAddresses();
for (i = 0; i < addresses.length; i++) 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) { utils.forEachSerial(tx.inputs, function(input, next) {
var key = input.prevout.hash + '/' + input.prevout.index; var key = input.prevout.hash + '/' + input.prevout.index;
@ -1457,20 +1462,20 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo
address = input.getAddress(); address = input.getAddress();
batch.del(prefix + 's/t/' + key); batch.del('s/' + key);
self.hasTX(input.prevout.hash, function(err, result) { self.hasTX(input.prevout.hash, function(err, result) {
if (err) if (err)
return next(err); return next(err);
if (result) { if (result) {
batch.put(prefix + 'u/t/' + key, input.coin.toRaw()); batch.put('c/' + key, input.coin.toRaw());
if (address) if (address)
batch.put(prefix + 'u/a/' + address + '/' + key, DUMMY); batch.put('C/' + address + '/' + key, DUMMY);
} else { } else {
batch.del(prefix + 'u/t/' + key); batch.del('c/' + key);
if (address) if (address)
batch.del(prefix + 'u/a/' + address + '/' + key); batch.del('C/' + address + '/' + key);
} }
next(); next();
@ -1484,10 +1489,10 @@ Mempool.prototype._removeUnchecked = function removeUnchecked(hash, callback, fo
key = hash + '/' + i; key = hash + '/' + i;
address = output.getAddress(); address = output.getAddress();
batch.del(prefix + 'u/t/' + key); batch.del('c/' + key);
if (address) if (address)
batch.del(prefix + 'u/a/' + address + '/' + key); batch.del('C/' + address + '/' + key);
} }
return batch.write(callback); return batch.write(callback);

View File

@ -7,6 +7,22 @@
module.exports = function(bcoin) { 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 bn = require('bn.js');
var utils = require('./utils'); var utils = require('./utils');
var assert = bcoin.utils.assert; var assert = bcoin.utils.assert;
@ -20,8 +36,6 @@ var BufferWriter = require('./writer');
* TXDB * TXDB
* @exports TXDB * @exports TXDB
* @constructor * @constructor
* @param {String} prefix - The prefix for
* the database (`txdb/` by default).
* @param {LowlevelUp} db * @param {LowlevelUp} db
* @param {Object?} options * @param {Object?} options
* @param {Boolean?} options.mapAddress - Map addresses to IDs. * @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.indexExtra - Index timestamps, heights, etc.
* @param {Boolean?} options.verify - Verify transactions as they * @param {Boolean?} options.verify - Verify transactions as they
* come in (note that this will not happen on the worker pool). * 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)) if (!(this instanceof TXDB))
return new TXDB(prefix, db, options); return new TXDB(db, options);
EventEmitter.call(this); EventEmitter.call(this);
@ -42,7 +55,6 @@ function TXDB(prefix, db, options) {
options = {}; options = {};
this.db = db; this.db = db;
this.prefix = prefix || 'txdb';
this.options = options; this.options = options;
this.busy = false; this.busy = false;
this.jobs = []; this.jobs = [];
@ -121,7 +133,6 @@ TXDB.prototype.getMap = function getMap(tx, callback) {
TXDB.prototype.mapAddresses = function mapAddresses(address, callback) { TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var table = {}; var table = {};
var iter; var iter;
@ -145,8 +156,8 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
} }
iter = this.db.iterator({ iter = this.db.iterator({
gte: prefix + 'a/' + address, gte: 'W/' + address,
lte: prefix + 'a/' + address + '~', lte: 'W/' + address + '~',
keys: true, keys: true,
values: false, values: false,
fillCache: 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); table[address].push(key);
next(); next();
@ -192,10 +203,9 @@ TXDB.prototype.mapAddresses = function mapAddresses(address, callback) {
*/ */
TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) { TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) {
var prefix = this.prefix + '/';
var p; var p;
this.db.get(prefix + 'o/' + key, function(err, buf) { this.db.get('o/' + key, function(err, buf) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -220,10 +230,9 @@ TXDB.prototype._addOrphan = function _addOrphan(key, hash, index, callback) {
TXDB.prototype._getOrphans = function _getOrphans(key, callback) { TXDB.prototype._getOrphans = function _getOrphans(key, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var p, orphans; 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') if (err && err.type !== 'NotFoundError')
return callback(err); return callback(err);
@ -297,7 +306,6 @@ TXDB.prototype.add = function add(tx, callback, force) {
// receiving txs out of order. // receiving txs out of order.
TXDB.prototype._add = function add(tx, map, callback, force) { TXDB.prototype._add = function add(tx, map, callback, force) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var updated = false; var updated = false;
var batch; var batch;
@ -324,30 +332,27 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
batch = self.db.batch(); batch = self.db.batch();
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put('t/' + hash, tx.toExtended());
if (self.options.indexExtra) { if (self.options.indexExtra) {
if (tx.ts === 0) { if (tx.ts === 0) {
assert(tx.ps > 0); assert(tx.ps > 0);
batch.put(prefix + 't/p/t/' + hash, DUMMY); batch.put('p/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else { } else {
batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
} }
if (self.options.indexAddress) { if (self.options.indexAddress) {
map.all.forEach(function(id) { map.all.forEach(function(id) {
batch.put(prefix + 't/a/' + id + '/' + hash, DUMMY); batch.put('T/' + id + '/' + hash, DUMMY);
if (tx.ts === 0) { if (tx.ts === 0) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); batch.put('P/' + id + '/' + hash, DUMMY);
batch.put( batch.put('M/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY);
} else { } else {
batch.put( batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); batch.put('M/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
batch.put(
prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY);
} }
}); });
} }
@ -388,12 +393,12 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
if (self.options.indexAddress && address) { if (self.options.indexAddress && address) {
map.table[address].forEach(function(id) { 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.put(prefix + 's/t/' + key, tx.hash()); batch.put('s/' + key, tx.hash());
return next(); return next();
} }
@ -442,7 +447,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
if (err) if (err)
return next(err); return next(err);
batch.put(prefix + 'o/' + key, orphans); batch.put('o/' + key, orphans);
return next(); return next();
}); });
@ -513,7 +518,7 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
if (!some) if (!some)
orphans = null; orphans = null;
self.db.del(prefix + 'o/' + key, finish); self.db.del('o/' + key, finish);
}); });
function finish(err) { function finish(err) {
@ -523,11 +528,11 @@ TXDB.prototype._add = function add(tx, map, callback, force) {
if (!orphans) { if (!orphans) {
if (self.options.indexAddress && address) { if (self.options.indexAddress && address) {
map.table[address].forEach(function(id) { 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; updated = true;
} }
@ -639,8 +644,7 @@ TXDB.prototype.isDoubleSpend = function isDoubleSpend(tx, callback) {
TXDB.prototype.isSpent = function isSpent(hash, index, callback) { TXDB.prototype.isSpent = function isSpent(hash, index, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/'; var key = 's/' + hash + '/' + index;
var key = prefix + 's/t/' + hash + '/' + index;
return this.db.get(key, function(err, hash) { return this.db.get(key, function(err, hash) {
if (err && err.type !== 'NotFoundError') 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) { TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var batch; var batch;
@ -698,20 +701,20 @@ TXDB.prototype._confirm = function _confirm(tx, map, callback, force) {
assert(tx.height >= 0); assert(tx.height >= 0);
assert(existing.ps > 0); assert(existing.ps > 0);
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put('t/' + hash, tx.toExtended());
if (self.options.indexExtra) { if (self.options.indexExtra) {
batch.del(prefix + 't/p/t/' + hash); batch.del('p/' + hash);
batch.put(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash, DUMMY); batch.put('h/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.del(prefix + 't/s/s/' + pad32(existing.ps) + '/' + hash); batch.del('m/' + pad32(existing.ps) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash, DUMMY); batch.put('m/' + pad32(tx.ts) + '/' + hash, DUMMY);
if (self.options.indexAddress) { if (self.options.indexAddress) {
map.all.forEach(function(id) { map.all.forEach(function(id) {
batch.del(prefix + 't/p/a/' + id + '/' + hash); batch.del('P/' + id + '/' + hash);
batch.put(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY); batch.put('H/' + id + '/' + pad32(tx.height) + '/' + hash, DUMMY);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(existing.ps) + '/' + hash); batch.del('M/' + id + '/' + pad32(existing.ps) + '/' + hash);
batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + hash, DUMMY); 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; coin.height = tx.height;
batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); batch.put('c/' + hash + '/' + i, coin.toRaw());
next(); next();
}); });
@ -836,7 +839,6 @@ TXDB.prototype.lazyRemove = function lazyRemove(tx, callback, force) {
TXDB.prototype._remove = function remove(tx, map, callback, force) { TXDB.prototype._remove = function remove(tx, map, callback, force) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var hash = tx.hash('hex'); var hash = tx.hash('hex');
var batch; var batch;
@ -848,26 +850,26 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
batch = this.db.batch(); batch = this.db.batch();
batch.del(prefix + 't/t/' + hash); batch.del('t/' + hash);
if (self.options.indexExtra) { if (self.options.indexExtra) {
if (tx.ts === 0) { if (tx.ts === 0) {
batch.del(prefix + 't/p/t/' + hash); batch.del('p/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash); batch.del('m/' + pad32(tx.ps) + '/' + hash);
} else { } else {
batch.del(prefix + 't/h/h/' + pad32(tx.height) + '/' + hash); batch.del('h/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(tx.ts) + '/' + hash); batch.del('m/' + pad32(tx.ts) + '/' + hash);
} }
if (self.options.indexAddress) { if (self.options.indexAddress) {
map.all.forEach(function(id) { map.all.forEach(function(id) {
batch.del(prefix + 't/a/' + id + '/' + hash); batch.del('T/' + id + '/' + hash);
if (tx.ts === 0) { if (tx.ts === 0) {
batch.del(prefix + 't/p/a/' + id + '/' + hash); batch.del('P/' + id + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash); batch.del('M/' + id + '/' + pad32(tx.ps) + '/' + hash);
} else { } else {
batch.del(prefix + 't/h/a/' + id + '/' + pad32(tx.height) + '/' + hash); batch.del('H/' + id + '/' + pad32(tx.height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(tx.ts) + '/' + 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) { if (self.options.indexAddress && address) {
map.table[address].forEach(function(id) { 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.put('c/' + key, input.coin.toRaw());
batch.del(prefix + 's/t/' + key); batch.del('s/' + key);
batch.del(prefix + 'o/' + key); batch.del('o/' + key);
}); });
tx.outputs.forEach(function(output, i) { tx.outputs.forEach(function(output, i) {
@ -914,11 +916,11 @@ TXDB.prototype._remove = function remove(tx, map, callback, force) {
if (self.options.indexAddress && address) { if (self.options.indexAddress && address) {
map.table[address].forEach(function(id) { 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) { 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) { TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var hash, batch, height, ts; var hash, batch, height, ts;
var unlock = this._lock(unconfirm, [tx, map, callback], force); 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.index = -1;
tx.block = null; tx.block = null;
batch.put(prefix + 't/t/' + hash, tx.toExtended()); batch.put('t/' + hash, tx.toExtended());
if (self.options.indexExtra) { if (self.options.indexExtra) {
batch.put(prefix + 't/p/t/' + hash, DUMMY); batch.put('p/' + hash, DUMMY);
batch.del(prefix + 't/h/h/' + pad32(height) + '/' + hash); batch.del('h/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/s/' + pad32(ts) + '/' + hash); batch.del('m/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/s/' + pad32(tx.ps) + '/' + hash, DUMMY); batch.put('m/' + pad32(tx.ps) + '/' + hash, DUMMY);
if (self.options.indexAddress) { if (self.options.indexAddress) {
map.all.forEach(function(id) { map.all.forEach(function(id) {
batch.put(prefix + 't/p/a/' + id + '/' + hash, DUMMY); batch.put('P/' + id + '/' + hash, DUMMY);
batch.del(prefix + 't/h/a/' + id + '/' + pad32(height) + '/' + hash); batch.del('H/' + id + '/' + pad32(height) + '/' + hash);
batch.del(prefix + 't/s/a/' + id + '/' + pad32(ts) + '/' + hash); batch.del('M/' + id + '/' + pad32(ts) + '/' + hash);
batch.put(prefix + 't/s/a/' + id + '/' + pad32(tx.ps) + '/' + hash, DUMMY); 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; coin.height = tx.height;
batch.put(prefix + 'u/t/' + hash + '/' + i, coin.toRaw()); batch.put('c/' + hash + '/' + i, coin.toRaw());
next(); next();
}); });
@ -1062,7 +1063,6 @@ TXDB.prototype._unconfirm = function unconfirm(tx, map, callback, force) {
TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) { TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var txs = []; var txs = [];
var iter; var iter;
@ -1094,8 +1094,8 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) {
} }
iter = this.db.iterator({ iter = this.db.iterator({
gte: address ? prefix + 't/a/' + address : prefix + 't/t', gte: address ? 'T/' + address : 't',
lte: address ? prefix + 't/a/' + address + '~' : prefix + 't/t~', lte: address ? 'T/' + address + '~' : 't~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1119,9 +1119,9 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) {
} }
if (address) if (address)
txs.push(key.split('/')[4]); txs.push(key.split('/')[2]);
else else
txs.push(key.split('/')[3]); txs.push(key.split('/')[1]);
next(); next();
}); });
@ -1136,7 +1136,6 @@ TXDB.prototype.getHistoryHashes = function getHistoryHashes(address, callback) {
TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, callback) { TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var txs = []; var txs = [];
var iter; var iter;
@ -1169,8 +1168,8 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal
} }
iter = this.db.iterator({ iter = this.db.iterator({
gte: address ? prefix + 't/p/a/' + address : prefix + 't/p/t', gte: address ? 'P/' + address : 'p',
lte: address ? prefix + 't/p/a/' + address + '~' : prefix + 't/p/t~', lte: address ? 'P/' + address + '~' : 'p~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1194,9 +1193,9 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal
} }
if (address) if (address)
txs.push(key.split('/')[5]); txs.push(key.split('/')[2]);
else else
txs.push(key.split('/')[4]); txs.push(key.split('/')[1]);
next(); next();
}); });
@ -1211,7 +1210,6 @@ TXDB.prototype.getUnconfirmedHashes = function getUnconfirmedHashes(address, cal
TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) { TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) {
var self = this; var self = this;
var prefix = this.prefix + '/';
var coins = []; var coins = [];
var iter; var iter;
@ -1242,11 +1240,11 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) {
iter = this.db.iterator({ iter = this.db.iterator({
gte: address gte: address
? prefix + 'u/a/' + address ? 'C/' + address
: prefix + 'u/t', : 'c',
lte: address lte: address
? prefix + 'u/a/' + address + '~' ? 'C/' + address + '~'
: prefix + 'u/t~', : 'c~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1272,9 +1270,9 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) {
key = key.split('/'); key = key.split('/');
if (address) if (address)
coins.push([key[4], +key[5]]); coins.push([key[2], +key[3]]);
else else
coins.push([key[3], +key[4]]); coins.push([key[1], +key[2]]);
next(); next();
}); });
@ -1293,7 +1291,6 @@ TXDB.prototype.getCoinHashes = function getCoinHashes(address, callback) {
*/ */
TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, options, callback) { TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, options, callback) {
var prefix = this.prefix + '/';
var txs = []; var txs = [];
var iter; var iter;
@ -1310,11 +1307,11 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, opt
iter = this.db.iterator({ iter = this.db.iterator({
gte: address gte: address
? prefix + 't/h/a/' + address + '/' + pad32(options.start) + '/' ? 'H/' + address + '/' + pad32(options.start) + '/'
: prefix + 't/h/h/' + pad32(options.start) + '/', : 'h/' + pad32(options.start) + '/',
lte: address lte: address
? prefix + 't/h/a/' + address + '/' + pad32(options.end) + '/~' ? 'H/' + address + '/' + pad32(options.end) + '/~'
: prefix + 't/h/h/' + pad32(options.end) + '/~', : 'h/' + pad32(options.end) + '/~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1340,9 +1337,9 @@ TXDB.prototype.getHeightRangeHashes = function getHeightRangeHashes(address, opt
} }
if (address) if (address)
txs.push(key.split('/')[6]); txs.push(key.split('/')[3]);
else else
txs.push(key.split('/')[5]); txs.push(key.split('/')[2]);
next(); next();
}); });
@ -1371,7 +1368,6 @@ TXDB.prototype.getHeightHashes = function getHeightHashes(height, callback) {
*/ */
TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callback) { TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callback) {
var prefix = this.prefix + '/';
var txs = []; var txs = [];
var iter; var iter;
@ -1384,11 +1380,11 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callba
iter = this.db.iterator({ iter = this.db.iterator({
gte: address gte: address
? prefix + 't/s/a/' + address + '/' + pad32(options.start) + '/' ? 'M/' + address + '/' + pad32(options.start) + '/'
: prefix + 't/s/s/' + pad32(options.start) + '/', : 'm/' + pad32(options.start) + '/',
lte: address lte: address
? prefix + 't/s/a/' + address + '/' + pad32(options.end) + '/~' ? 'M/' + address + '/' + pad32(options.end) + '/~'
: prefix + 't/s/s/' + pad32(options.end) + '/~', : 'm/' + pad32(options.end) + '/~',
keys: true, keys: true,
values: false, values: false,
fillCache: false, fillCache: false,
@ -1414,9 +1410,9 @@ TXDB.prototype.getRangeHashes = function getRangeHashes(address, options, callba
} }
if (address) if (address)
txs.push(key.split('/')[6]); txs.push(key.split('/')[3]);
else else
txs.push(key.split('/')[5]); txs.push(key.split('/')[2]);
next(); next();
}); });
@ -1740,8 +1736,7 @@ TXDB.prototype.fillCoins = function fillCoins(tx, callback) {
*/ */
TXDB.prototype.getTX = function getTX(hash, callback) { TXDB.prototype.getTX = function getTX(hash, callback) {
var prefix = this.prefix + '/'; var key = 't/' + hash;
var key = prefix + 't/t/' + hash;
this.db.get(key, function(err, tx) { this.db.get(key, function(err, tx) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')
@ -1783,8 +1778,7 @@ TXDB.prototype.hasTX = function hasTX(hash, callback) {
*/ */
TXDB.prototype.getCoin = function getCoin(hash, index, callback) { TXDB.prototype.getCoin = function getCoin(hash, index, callback) {
var prefix = this.prefix + '/'; var key = 'c/' + hash + '/' + index;
var key = prefix + 'u/t/' + hash + '/' + index;
this.db.get(key, function(err, coin) { this.db.get(key, function(err, coin) {
if (err && err.type !== 'NotFoundError') if (err && err.type !== 'NotFoundError')

View File

@ -7,6 +7,13 @@
module.exports = function(bcoin) { 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 EventEmitter = require('events').EventEmitter;
var utils = require('./utils'); var utils = require('./utils');
var assert = utils.assert; var assert = utils.assert;
@ -108,7 +115,7 @@ WalletDB.prototype._init = function _init() {
self.loaded = true; self.loaded = true;
}); });
this.tx = new bcoin.txdb('w', this.db, { this.tx = new bcoin.txdb(this.db, {
indexExtra: true, indexExtra: true,
indexAddress: true, indexAddress: true,
mapAddress: true, mapAddress: true,
@ -366,7 +373,7 @@ WalletDB.prototype.saveJSON = function saveJSON(id, json, callback) {
if (json) { if (json) {
batch = self.db.batch(); batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) { 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) { return batch.write(function(err) {
if (err) if (err)
@ -402,7 +409,7 @@ WalletDB.prototype.removeJSON = function removeJSON(id, callback) {
if (json) { if (json) {
batch = self.db.batch(); batch = self.db.batch();
Object.keys(json.addressMap).forEach(function(address) { 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) { return batch.write(function(err) {
if (err) if (err)
@ -423,7 +430,7 @@ WalletDB.prototype.removeJSON = function removeJSON(id, callback) {
*/ */
WalletDB.prototype._getDB = function _getDB(id, callback) { WalletDB.prototype._getDB = function _getDB(id, callback) {
var key = 'w/w/' + id; var key = 'w/' + id;
callback = utils.ensure(callback); callback = utils.ensure(callback);
@ -453,7 +460,7 @@ WalletDB.prototype._getDB = function _getDB(id, callback) {
*/ */
WalletDB.prototype._saveDB = function _saveDB(id, json, callback) { WalletDB.prototype._saveDB = function _saveDB(id, json, callback) {
var key = 'w/w/' + id; var key = 'w/' + id;
var data; var data;
callback = utils.ensure(callback); callback = utils.ensure(callback);
@ -477,7 +484,7 @@ WalletDB.prototype._saveDB = function _saveDB(id, json, callback) {
WalletDB.prototype._removeDB = function _removeDB(id, callback) { WalletDB.prototype._removeDB = function _removeDB(id, callback) {
var self = this; var self = this;
var key = 'w/w/' + id; var key = 'w/' + id;
callback = utils.ensure(callback); callback = utils.ensure(callback);
@ -691,18 +698,18 @@ WalletDB.prototype.update = function update(wallet, address) {
batch = this.db.batch(); batch = this.db.batch();
batch.put( batch.put(
'w/a/' + address.getKeyAddress() + '/' + wallet.id, 'W/' + address.getKeyAddress() + '/' + wallet.id,
DUMMY); DUMMY);
if (address.type === 'multisig') { if (address.type === 'multisig') {
batch.put( batch.put(
'w/a/' + address.getScriptAddress() + '/' + wallet.id, 'W/' + address.getScriptAddress() + '/' + wallet.id,
DUMMY); DUMMY);
} }
if (address.witness) { if (address.witness) {
batch.put( batch.put(
'w/a/' + address.getProgramAddress() + '/' + wallet.id, 'W/' + address.getProgramAddress() + '/' + wallet.id,
DUMMY); DUMMY);
} }

View File

@ -1,13 +1,13 @@
var bn = require('bn.js'); var bn = require('bn.js');
var bcoin = require('../')({ db: 'memory' }); var bcoin = require('../')();
var constants = bcoin.protocol.constants; var constants = bcoin.protocol.constants;
var utils = bcoin.utils; var utils = bcoin.utils;
var assert = require('assert'); var assert = require('assert');
var opcodes = constants.opcodes; var opcodes = constants.opcodes;
describe('Mempool', function() { describe('Mempool', function() {
var chain = new bcoin.chain(); var chain = new bcoin.chain({ db: 'memory' });
var mempool = new bcoin.mempool({ chain: chain }); var mempool = new bcoin.mempool({ chain: chain, db: 'memory' });
mempool.on('error', function() {}); mempool.on('error', function() {});
it('should open mempool', function(cb) { it('should open mempool', function(cb) {

View File

@ -1,5 +1,5 @@
var bn = require('bn.js'); 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 constants = bcoin.protocol.constants;
var utils = bcoin.utils; var utils = bcoin.utils;
var assert = require('assert'); var assert = require('assert');
@ -24,7 +24,7 @@ var dummyInput = {
}; };
describe('Wallet', function() { 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) { it('should open walletdb', function(cb) {
wdb.open(cb); wdb.open(cb);
@ -554,12 +554,4 @@ describe('Wallet', function() {
it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', function(cb) { it('should verify 2-of-3 witnessscripthash tx with bullshit nesting', function(cb) {
multisig(true, true, 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);
});
});
}); });