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 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;

View File

@ -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();
});

View File

@ -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);

View File

@ -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')

View File

@ -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);
}

View File

@ -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) {

View File

@ -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);
});
});
});