This commit is contained in:
Christopher Jeffrey 2016-03-11 15:53:15 -08:00
parent 2c861aff82
commit 8f85f0b5c9
6 changed files with 1131 additions and 422 deletions

View File

@ -16,11 +16,11 @@ var pad32 = utils.pad32;
* BlockDB
*/
function BlockDB(node, options) {
function BlockDB(options, db) {
var self = this;
if (!(this instanceof BlockDB))
return new BlockDB(node, options);
return new BlockDB(options);
EventEmitter.call(this);
@ -32,12 +32,7 @@ function BlockDB(node, options) {
this.keepBlocks = options.keepBlocks || 288;
this.prune = !!options.prune;
this.node = node;
this.db = bcoin.ldb('block', {
cacheSize: 16 * 1024 * 1024,
writeBufferSize: 8 * 1024 * 1024
});
this.db = db;
}
utils.inherits(BlockDB, EventEmitter);
@ -52,9 +47,8 @@ BlockDB.prototype.close = function close(callback) {
});
};
BlockDB.prototype.saveBlock = function saveBlock(block, callback) {
BlockDB.prototype.saveBlock = function saveBlock(block, batch, callback) {
var self = this;
var batch = this.batch();
batch.put('b/b/' + block.hash('hex'), block.toCompact());
@ -62,7 +56,9 @@ BlockDB.prototype.saveBlock = function saveBlock(block, callback) {
batch.put('t/t/' + tx.hash('hex'), tx.toExtended());
});
self.connectBlock(block, function(err) {
self.connectBlock(block, batch, callback);
return;
self.connectBlock(block, batch, function(err) {
if (err)
return callback(err);
@ -72,40 +68,36 @@ BlockDB.prototype.saveBlock = function saveBlock(block, callback) {
// Check for now-fully-spent txs. Try to remove
// them or queue them up for future deletion if
// it is currently unsafe to remove them.
self._pruneBlock(block, function(err) {
self._pruneBlock(block, batch, function(err) {
if (err)
return callback(err);
return callback(null, block);
});
}, batch);
});
};
BlockDB.prototype.removeBlock = function removeBlock(hash, callback) {
BlockDB.prototype.removeBlock = function removeBlock(hash, batch, callback) {
var self = this;
this._getTXBlock(hash, function(err, block) {
var batch;
if (err)
return callback(err);
if (!block)
return callback();
batch = self.batch();
batch.del('b/b/' + block.hash('hex'));
block.txs.forEach(function(tx, i) {
batch.del('t/t/' + tx.hash('hex'));
});
self.disconnectBlock(block, callback, batch);
self.disconnectBlock(block, batch, callback);
});
};
BlockDB.prototype.connectBlock = function connectBlock(block, callback, batch) {
BlockDB.prototype.connectBlock = function connectBlock(block, batch, callback) {
var self = this;
this._getCoinBlock(block, function(err, block) {
@ -114,19 +106,8 @@ BlockDB.prototype.connectBlock = function connectBlock(block, callback, batch) {
if (err)
return callback(err);
if (!block) {
assert(!batch);
if (!block)
return callback();
}
if (!batch)
batch = self.batch();
batch.put('b/h/' + pad32(block.height), block.hash());
height = new Buffer(4);
utils.writeU32(height, block.height, 0);
batch.put('b/t', height);
block.txs.forEach(function(tx, i) {
var hash = tx.hash('hex');
@ -178,40 +159,25 @@ BlockDB.prototype.connectBlock = function connectBlock(block, callback, batch) {
});
});
batch.write(function(err) {
if (err)
return callback(err);
self.emit('save block', block);
return callback(null, block);
});
self.emit('add block', block);
return callback(null, block);
});
};
BlockDB.prototype.disconnectBlock = function disconnectBlock(hash, callback, batch) {
BlockDB.prototype.disconnectBlock = function disconnectBlock(hash, batch, callback) {
var self = this;
this._getTXBlock(hash, function(err, block) {
var height;
if (err)
return callback(err);
if (!block) {
assert(!batch);
if (!block)
return callback();
}
if (!batch)
batch = self.batch();
if (typeof hash === 'string')
assert(block.hash('hex') === hash);
height = new Buffer(4);
utils.writeU32(height, block.height - 1, 0);
batch.put('b/t', height);
batch.del('b/h/' + pad32(block.height));
block.txs.forEach(function(tx, i) {
var hash = tx.hash('hex');
var uniq = {};
@ -265,12 +231,8 @@ BlockDB.prototype.disconnectBlock = function disconnectBlock(hash, callback, bat
});
});
batch.write(function(err) {
if (err)
return callback(err);
self.emit('remove block', block);
return callback(null, block);
});
self.emit('remove block', block);
return callback(null, block);
});
};
@ -692,7 +654,7 @@ BlockDB.prototype._getHash = function _getHash(height, callback) {
if (typeof height === 'string')
return callback(null, height);
this.db.get('b/h/' + pad32(height), function(err, hash) {
this.db.get('c/h/' + pad32(height), function(err, hash) {
if (err && err.type !== 'NotFoundError')
return callback(err);
if (!hash)
@ -806,6 +768,10 @@ BlockDB.prototype.isUnspentTX = function isUnspentTX(hash, callback) {
BlockDB.prototype.isSpentTX = function isSpentTX(hash, callback) {
var spent = true;
// Important!
if (hash.hash)
hash = hash.hash('hex');
var iter = this.db.db.iterator({
gte: 'u/t/' + hash,
lte: 'u/t/' + hash + '~',
@ -828,7 +794,8 @@ BlockDB.prototype.isSpentTX = function isSpentTX(hash, callback) {
spent = false;
next();
// IMPORTANT!
iter.end(done);
});
})();
@ -864,72 +831,9 @@ BlockDB.prototype.isSpent = function isSpent(hash, index, callback) {
});
};
BlockDB.prototype.getHeight = function getHeight(callback) {
BlockDB.prototype._pruneBlock = function _pruneBlock(block, batch, callback) {
var self = this;
return this.db.get('b/t', function(err, height) {
if (err && err.type !== 'NotFoundError')
return callback(err);
if (!height)
return callback(null, -1);
return callback(null, utils.readU32(height, 0));
});
};
BlockDB.prototype.getTipHash = function getTipHash(callback) {
return this.getHeight(function(err, height) {
if (err)
return callback(err);
if (height === -1)
return callback();
return self.db.get('b/h/' + pad32(height), function(err, hash) {
if (err && err.type !== 'NotFoundError')
return callback(err);
if (!hash)
return callback();
return callback(null, utils.toHex(hash));
});
});
};
BlockDB.prototype.reset = function reset(height, callback, emit) {
var self = this;
this.getHeight(function(err, currentHeight) {
if (err)
return callback(err);
if (currentHeight < height)
return callback(new Error('Cannot reset to height ' + height));
(function next() {
if (currentHeight === height)
return callback();
self.removeBlock(currentHeight, function(err, block) {
if (err)
return callback(err);
// Emit the blocks we removed.
if (emit && block)
emit(block);
currentHeight--;
next();
});
})();
});
};
BlockDB.prototype._pruneBlock = function _pruneBlock(block, callback) {
var self = this;
var batch = this.batch();
// For much more aggressive pruning, we could delete
// the block headers 288 blocks before this one here as well.
@ -942,7 +846,7 @@ BlockDB.prototype._pruneBlock = function _pruneBlock(block, callback) {
self._pruneQueue(block, batch, function(err) {
if (err)
return callback(err);
return batch.write(callback);
return callback();
});
});
};

View File

@ -34,8 +34,7 @@ function Chain(node, options) {
this.node = node;
this.loading = false;
this.mempool = node.mempool;
this.blockdb = node.blockdb;
this.db = new bcoin.chaindb(node, this, options);
this.db = new bcoin.chaindb(this, options);
this.busy = false;
this.jobs = [];
this.pending = [];
@ -132,41 +131,47 @@ Chain.prototype._init = function _init() {
utils.debug('Warning: %d (%dmb) orphans cleared!', count, utils.mb(size));
});
this.db.on('add entry', function(entry) {
self.emit('add entry', entry);
});
this.db.on('remove entry', function(entry) {
self.emit('remove entry', entry);
});
this.db.on('add block', function(block) {
self.emit('add block', block);
});
this.db.on('remove block', function(block) {
self.emit('remove block', block);
});
this.loading = true;
utils.debug('Chain is loading.');
this._ensureGenesis(function(err) {
if (err)
throw err;
self._preload(function(err, start) {
if (err) {
utils.debug('Preloading chain failed.');
utils.debug('Reason: %s', err.message);
}
self._preload(function(err, start) {
if (err) {
utils.debug('Preloading chain failed.');
utils.debug('Reason: %s', err.message);
}
self.db.load(function(err) {
if (err)
throw err;
self.db.load(function(err) {
self.db.getTip(function(err, tip) {
if (err)
throw err;
self._syncHeight(function(err) {
if (err)
throw err;
assert(tip);
self.db.getTip(function(err, tip) {
if (err)
throw err;
self.tip = tip;
self.height = tip.height;
assert(tip);
self.tip = tip;
self.height = tip.height;
self.loading = false;
self.emit('load');
});
});
self.loading = false;
self.emit('load');
});
});
});
@ -230,37 +235,6 @@ Chain.prototype._lock = function _lock(func, args, force) {
};
};
Chain.prototype._ensureGenesis = function _ensureGenesis(callback) {
var self = this;
callback = utils.asyncify(callback);
if (!this.blockdb)
return callback();
self.blockdb.hasBlock(network.genesis.hash, function(err, result) {
var genesis;
if (err)
return callback(err);
if (result)
return callback();
utils.debug('BlockDB does not have genesis block. Adding.');
genesis = bcoin.block.fromRaw(network.genesisBlock, 'hex');
genesis.height = 0;
self.blockdb.saveBlock(genesis, function(err) {
if (err)
return callback(err);
return callback();
});
});
};
// Stream headers from electrum.org for quickly
// preloading the chain. Electrum.org stores
// headers in the standard block header format,
@ -398,24 +372,6 @@ Chain.prototype._preload = function _preload(callback) {
});
};
Chain.prototype._saveBlock = function _saveBlock(block, callback) {
var self = this;
if (!this.blockdb)
return utils.nextTick(callback);
this.blockdb.saveBlock(block, callback);
};
Chain.prototype._removeBlock = function _removeBlock(tip, callback) {
var self = this;
if (!this.blockdb)
return utils.nextTick(callback);
this.blockdb.removeBlock(tip, callback);
};
Chain.prototype._verifyContext = function _verifyContext(block, prev, callback) {
var self = this;
@ -605,18 +561,18 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba
var self = this;
var height = prev.height + 1;
if (!this.blockdb || block.type !== 'block')
if (this.options.spv || block.type !== 'block')
return callback(null, true);
if (block.isGenesis())
return callback(null, true);
// Check all transactions
utils.every(block.txs, function(tx, next) {
utils.everySerial(block.txs, function(tx, next) {
var hash = tx.hash('hex');
// BIP30 - Ensure there are no duplicate txids
self.blockdb.isUnspentTX(hash, function(err, result) {
self.db.isUnspentTX(hash, function(err, result) {
if (err)
return next(err);
@ -639,7 +595,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
var height = prev.height + 1;
var scriptCheck = true;
if (!this.blockdb || block.type !== 'block')
if (this.options.spv || block.type !== 'block')
return callback(null, true);
if (block.isGenesis())
@ -652,13 +608,14 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac
scriptCheck = false;
}
this.blockdb.fillBlock(block, function(err) {
this.db.fillBlock(block, function(err) {
var i, j, input, hash;
var sigops = 0;
if (err)
return callback(err);
// Check all transactions
for (i = 0; i < block.txs.length; i++) {
tx = block.txs[i];
@ -827,36 +784,8 @@ Chain.prototype._reorganize = function _reorganize(entry, callback) {
assert(entries.length > 0);
utils.forEachSerial(entries, function(entry, next) {
self.db.disconnect(entry, function(err) {
if (err)
return next(err);
self.emit('remove entry', entry);
next();
});
}, function(err) {
if (err)
return callback(err);
if (!self.blockdb)
return callback();
utils.forEachSerial(entries, function(entry, next) {
self.blockdb.disconnectBlock(entry.hash, function(err, block) {
if (err)
return next(err);
self.emit('remove block', block);
next();
});
}, function(err) {
if (err)
return callback(err);
return callback();
});
});
self.db.disconnect(entry, next);
}, callback);
}
}
@ -885,39 +814,8 @@ Chain.prototype._reorganize = function _reorganize(entry, callback) {
assert(entries.length > 0);
utils.forEachSerial(entries, function(entry, next) {
self.db.connect(entry, function(err) {
if (err)
return next(err);
self.emit('add entry', entry);
return next();
});
}, function(err) {
if (err)
return callback(err);
if (!self.blockdb)
return callback();
utils.forEachSerial(entries, function(err, entry) {
self.blockdb.connectBlock(entry.hash, function(err, block) {
if (err)
return callback(err);
assert(block);
self.emit('add block', block);
next();
});
}, function(err) {
if (err)
return callback(err);
return callback();
});
});
self.db.connect(entry, next);
}, callback);
}
}
@ -951,11 +849,8 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
// Start fsyncing writes once we're no
// longer dealing with historical data.
if (this.isFull()) {
if (this.isFull())
this.db.fsync = true;
if (this.blockdb)
this.blockdb.fsync = true;
}
if (!this.tip) {
if (entry.hash !== network.genesis.hash)
@ -972,19 +867,14 @@ Chain.prototype._setBestChain = function _setBestChain(entry, block, callback) {
if (err)
return callback(err);
self._saveBlock(block, function(err) {
self.db.save(entry, block, function(err) {
if (err)
return callback(err);
self.db.save(entry, function(err) {
if (err)
return callback(err);
self.tip = entry;
self.height = entry.height;
self.tip = entry;
self.height = entry.height;
return callback();
});
return callback();
});
}
};
@ -1014,74 +904,7 @@ Chain.prototype.reset = function reset(height, callback, force) {
if (err)
return done(err);
if (!self.blockdb)
return done();
self.blockdb.reset(height, function(err) {
if (err)
return done(err);
return done();
}, function(block) {
self.emit('remove block', block);
});
}, function(entry) {
self.emit('remove entry', entry);
});
};
Chain.prototype._syncHeight = function _syncHeight(callback, force) {
var self = this;
var chainHeight;
var unlock = this._lock(_syncHeight, [callback], force);
if (!unlock)
return;
callback = utils.ensure(callback);
function done(err, result) {
unlock();
callback(err, result);
}
this.db.getChainHeight(function(err, chainHeight) {
if (err)
return done(err);
if (chainHeight == null || chainHeight < 0)
return done(new Error('Bad chain height.'));
if (!self.blockdb)
return done();
self.blockdb.getHeight(function(err, blockHeight) {
if (err)
return done(err);
if (blockHeight < 0)
return done(new Error('Bad block height.'));
if (blockHeight === chainHeight)
return done();
utils.debug('ChainDB and BlockDB are out of sync.');
if (blockHeight < chainHeight) {
utils.debug('ChainDB is higher than BlockDB. Syncing...');
return self.db.reset(blockHeight, done);
}
if (blockHeight > chainHeight) {
utils.debug('BlockDB is higher than ChainDB. Syncing...');
self.blockdb.reset(chainHeight, function(err) {
if (err)
return done(err);
return done();
});
}
});
return done();
});
};
@ -1290,6 +1113,13 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
}
}
// Update the block height
// IMPORTANT!!!!!
block.height = height;
block.txs.forEach(function(tx) {
tx.height = height;
});
// Do "contextual" verification on our block
// now that we're certain its previous
// block is in the chain.
@ -1326,12 +1156,6 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
if (entry.chainwork.cmp(self.tip.chainwork) <= 0)
return done();
// Update the block height
block.height = entry.height;
block.txs.forEach(function(tx) {
tx.height = entry.height;
});
// Attempt to add block to the chain index.
self._setBestChain(entry, block, function(err) {
if (err)
@ -1347,8 +1171,6 @@ Chain.prototype.add = function add(initial, peer, callback, force) {
if (block.hash('hex') !== initial.hash('hex'))
self.emit('resolved', block, entry, peer);
self.emit('add block', block);
// No orphan chain.
if (!self.orphan.map[hash])
return done();

File diff suppressed because it is too large Load Diff

View File

@ -41,13 +41,10 @@ Fullnode.prototype._init = function _init() {
this.loading = true;
// BlockDB technically needs access to the
// chain, but that's only once it's being
// used for tx retrieval.
this.blockdb = new bcoin.blockdb(this, {
cache: false,
this.chain = new bcoin.chain(this, {
preload: false,
fsync: false,
prune: true
prune: false
});
// Mempool needs access to blockdb.
@ -55,12 +52,6 @@ Fullnode.prototype._init = function _init() {
rbf: false
});
// Chain needs access to blockdb.
this.chain = new bcoin.chain(this, {
preload: false,
fsync: false
});
// Pool needs access to the chain.
this.pool = new bcoin.pool(this, {
witness: this.network.type === 'segnet',
@ -206,11 +197,11 @@ Fullnode.prototype.scanWallet = function scanWallet(wallet, callback) {
};
Fullnode.prototype.getBlock = function getBlock(hash, callback) {
this.blockdb.getBlock(hash, callback);
this.chain.db.getBlock(hash, callback);
};
Fullnode.prototype.getFullBlock = function getFullBlock(hash, callback) {
this.blockdb.getFullBlock(hash, callback);
this.chain.db.getFullBlock(hash, callback);
};
Fullnode.prototype.getCoin = function getCoin(hash, index, callback) {
@ -226,7 +217,7 @@ Fullnode.prototype.getCoin = function getCoin(hash, index, callback) {
if (this.mempool.isSpent(hash, index))
return callback(null, null);
this.blockdb.getCoin(hash, index, function(err, coin) {
this.chain.db.getCoin(hash, index, function(err, coin) {
if (err)
return callback(err);
@ -245,7 +236,7 @@ Fullnode.prototype.getCoinByAddress = function getCoinByAddress(addresses, callb
mempool = this.mempool.getCoinsByAddress(addresses);
this.blockdb.getCoinsByAddress(addresses, function(err, coins) {
this.chain.db.getCoinsByAddress(addresses, function(err, coins) {
if (err)
return callback(err);
@ -267,7 +258,7 @@ Fullnode.prototype.getTX = function getTX(hash, callback) {
if (tx)
return callback(null, tx);
this.blockdb.getTX(hash, function(err, tx) {
this.chain.db.getTX(hash, function(err, tx) {
if (err)
return callback(err);
@ -286,7 +277,7 @@ Fullnode.prototype.isSpent = function isSpent(hash, index, callback) {
if (this.mempool.isSpent(hash, index))
return callback(null, true);
this.blockdb.isSpent(hash, index, callback);
this.chain.db.isSpent(hash, index, callback);
};
Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback) {
@ -297,7 +288,7 @@ Fullnode.prototype.getTXByAddress = function getTXByAddress(addresses, callback)
mempool = this.mempool.getTXByAddress(addresses);
this.blockdb.getTXByAddress(addresses, function(err, txs) {
this.chain.db.getTXByAddress(addresses, function(err, txs) {
if (err)
return callback(err);
@ -311,7 +302,7 @@ Fullnode.prototype.fillCoin = function fillCoin(tx, callback) {
if (this.mempool.fillCoin(tx))
return callback();
this.blockdb.fillCoin(tx, callback);
this.chain.db.fillCoin(tx, callback);
};
Fullnode.prototype.fillTX = function fillTX(tx, callback) {
@ -320,7 +311,7 @@ Fullnode.prototype.fillTX = function fillTX(tx, callback) {
if (this.mempool.fillTX(tx))
return callback();
this.blockdb.fillTX(tx, callback);
this.chain.db.fillTX(tx, callback);
};
/**

View File

@ -27,7 +27,7 @@ function Mempool(node, options) {
this.options = options;
this.node = node;
this.blockdb = node.blockdb;
this.chain = node.chain;
this.txs = {};
this.spent = {};
@ -214,7 +214,7 @@ Mempool.prototype.addTX = function addTX(tx, peer, callback) {
this._lockTX(tx);
this.blockdb.fillCoin(tx, function(err) {
this.chain.fillCoin(tx, function(err) {
var i, input, dup, height, ts, priority;
self._unlockTX(tx);

View File

@ -1495,7 +1495,7 @@ utils.forEach = function forEach(arr, iter, callback) {
utils.forRangeSerial = function forRangeSerial(from, to, iter, callback) {
var called = false;
callback = utils.asyncify(callback);
callback = utils.ensure(callback);
(function next(err) {
assert(!called);
@ -1518,7 +1518,7 @@ utils.forEachSerial = function forEachSerial(arr, iter, callback) {
var i = 0;
var called = false;
callback = utils.asyncify(callback);
callback = utils.ensure(callback);
(function next(err) {
var item;
@ -1571,7 +1571,7 @@ utils.everySerial = function everySerial(arr, iter, callback) {
var i = 0;
var called = false;
callback = utils.asyncify(callback);
callback = utils.ensure(callback);
(function next(err, res) {
var item;
@ -1580,7 +1580,7 @@ utils.everySerial = function everySerial(arr, iter, callback) {
called = true;
return callback(err);
}
if (!result) {
if (!res) {
called = true;
return callback(null, false);
}
@ -1593,7 +1593,7 @@ utils.everySerial = function everySerial(arr, iter, callback) {
utils.nextTick(function() {
iter(item, next, i - 1);
});
})();
})(null, true);
};
utils.mb = function mb(size) {