From 0191acd5306ae72108f89a2ac10276370ef5b5eb Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 16 Feb 2016 17:12:06 -0800 Subject: [PATCH] blockdb reset height. --- lib/bcoin.js | 1 + lib/bcoin/blockdb.js | 182 ++++++++++++++++++++++++++++++++++++++++--- lib/bcoin/chain.js | 40 +++++++++- lib/bcoin/chaindb.js | 70 +++++++++++++++-- 4 files changed, 270 insertions(+), 23 deletions(-) diff --git a/lib/bcoin.js b/lib/bcoin.js index c4206f26..03f33427 100644 --- a/lib/bcoin.js +++ b/lib/bcoin.js @@ -24,6 +24,7 @@ if (!bcoin.isBrowser) { bcoin.fs = require('f' + 's'); bcoin.crypto = require('cry' + 'pto'); bcoin.net = require('n' + 'et'); + bcoin.cp = require('child_' + 'process'); } bcoin.elliptic = elliptic; diff --git a/lib/bcoin/blockdb.js b/lib/bcoin/blockdb.js index dcf93914..cd09b2dd 100644 --- a/lib/bcoin/blockdb.js +++ b/lib/bcoin/blockdb.js @@ -63,6 +63,21 @@ function BlockDB(options) { inherits(BlockDB, EventEmitter); +BlockDB.prototype.close = function close(callback) { + var self = this; + this.index.close(function(err) { + if (err) + return callback(err); + + self.data.closeAsync(function(err) { + if (err) + return callback(err); + + return callback(); + }); + }); +}; + BlockDB.prototype.migrate = function migrate(blockSize, compression, callback) { var options, db, pending, stream, total, done; @@ -251,14 +266,23 @@ BlockDB.prototype.removeBlock = function removeBlock(hash, callback) { batch = self.index.batch(); - batch.del('b/b/' + hash); + if (typeof hash === 'string') + assert(block.hash('hex') === hash); + + batch.del('b/b/' + block.hash('hex')); batch.del('b/h/' + block.height); function done() { batch.write(function(err) { if (err) return callback(err); - return callback(null, block); + // TODO: Add check to make sure we + // can ONLY remove the last block. + self.data.truncateAsync(block._fileOffset, function(err) { + if (err) + return callback(err); + return callback(null, block); + }); }); } @@ -810,6 +834,29 @@ BlockDB.prototype.getBlock = function getBlock(hash, callback) { }); }; +BlockDB.prototype.hasBlock = function hasBlock(hash, callback) { + var self = this; + var id = 'b/b/' + value; + + if (typeof hash === 'number') + id = 'b/h/' + value; + + this.index.get(id, function(err, record) { + if (err && err.type !== 'NotFoundError') + return callback(err); + + if (!record) + return callback(null, false); + + record = self.parseOffset(record); + + if (self.data.size < record.offset + record.size) + return callback(null, false); + + return callback(null, true); + }); +}; + BlockDB.prototype.hasCoin = function hasCoin(hash, index, callback) { var id = 'u/t/' + hash + '/' + index; @@ -817,7 +864,15 @@ BlockDB.prototype.hasCoin = function hasCoin(hash, index, callback) { if (err && err.type !== 'NotFoundError') return callback(err); - return callback(null, record ? true : false); + if (!record) + return callback(null, false); + + record = self.parseOffset(record); + + if (self.data.size < record.offset + record.size) + return callback(null, false); + + return callback(null, true); }); }; @@ -828,7 +883,15 @@ BlockDB.prototype.hasTX = function hasTX(hash, callback) { if (err && err.type !== 'NotFoundError') return callback(err); - return callback(null, record ? true : false); + if (!record) + return callback(null, false); + + record = self.parseOffset(record); + + if (self.data.size < record.offset + record.size) + return callback(null, false); + + return callback(null, true); }); }; @@ -841,6 +904,57 @@ BlockDB.prototype.isSpent = function isSpent(hash, index, callback) { }); }; +BlockDB.prototype.getHeight = function getHeight(callback) { + var self = this; + var maxHeight = -1; + var stream; + + callback = utils.asyncify(callback); + + stream = this.index.createKeyStream({ + start: 'b/h', + end: 'b/h~' + }); + + stream.on('data', function(data) { + var parts = data.key.split('/').slice(2); + var height = +parts[0]; + if (height > maxHeight) + maxHeight = height; + }); + + stream.on('error', function(err) { + return callback(err); + }); + + stream.on('end', function() { + return callback(null, maxHeight); + }); +}; + +BlockDB.prototype.resetHeight = function resetHeight(height, callback) { + 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 (height === currentHeight) + return callback(); + + self.removeBlock(height, function(err, block) { + if (err) + return callback(err); + height--; + next(); + }); + })(); + }); +}; + /** * BlockData */ @@ -888,7 +1002,34 @@ BlockData.prototype._init = function _init() { this.fd = fs.openSync(this.file, 'r+'); }; -BlockData.prototype._malloc = function(size) { +BlockData.prototype.closeSync = function closeSync() { + if (!bcoin.fs) { + this.ramdisk = null; + return; + } + fs.closeSync(this.fd); + this.fd = null; +}; + +BlockData.prototype.closeAsync = function closeAsync(callback) { + var self = this; + + callback = utils.asyncify(callback); + + if (!bcoin.fs) { + this.ramdisk = null; + return callback(); + } + + fs.close(this.fd, function(err) { + if (err) + return callback(err); + self.fd = null; + return callback(); + }); +}; + +BlockData.prototype._malloc = function _malloc(size) { if (size > 500) return new Buffer(size); @@ -903,7 +1044,7 @@ BlockData.prototype._malloc = function(size) { return this._bufferPool[size]; }; -BlockData.prototype._free = function(buf) { +BlockData.prototype._free = function _free(buf) { if (this._bufferPool.used[buf.length] === buf) { assert(this._bufferPool[buf.length] === buf); delete this._bufferPool.used[buf.length]; @@ -967,15 +1108,34 @@ BlockData.prototype.saveAsync = function saveAsync(data, callback) { }); }; -BlockData.prototype.truncate = function truncate(size) { - this.size = size; - +BlockData.prototype.truncateSync = function truncateSync(size) { if (!bcoin.fs) { - this.ramdisk.truncate(this.size); + this.ramdisk.truncate(size); + this.size = size; return; } - fs.ftruncateSync(this.fd, this.size); + fs.ftruncateSync(this.fd, size); + this.size = size; +}; + +BlockData.prototype.truncateAsync = function truncateAsync(size, callback) { + var self = this; + + callback = utils.asyncify(callback); + + if (!bcoin.fs) { + this.ramdisk.truncate(size); + this.size = size; + return callback(); + } + + fs.ftruncate(this.fd, size, function(err) { + if (err) + return callback(err); + self.size = size; + return callback(); + }); }; BlockData.prototype._readSync = function _readSync(size, offset) { diff --git a/lib/bcoin/chain.js b/lib/bcoin/chain.js index d2f143b9..63663fa6 100644 --- a/lib/bcoin/chain.js +++ b/lib/bcoin/chain.js @@ -110,6 +110,11 @@ Chain.prototype._init = function _init() { utils.debug('Starting chain load at height: %s', i); + function doneForReal() { + self.loading = false; + self.emit('load'); + } + function done(height) { if (height != null) { utils.debug( @@ -119,8 +124,33 @@ Chain.prototype._init = function _init() { } else { utils.debug('Chain successfully loaded.'); } - self.loading = false; - self.emit('load'); + + if (!self.blockdb) + return doneForReal(); + + return doneForReal(); + + self.blockdb.getHeight(function(err, height) { + if (err) + throw err; + + if (height === self.tip.height) + return doneForReal(); + + utils.debug('ChainDB and BlockDB are out of sync. Syncing...'); + + if (height < self.tip.height) + return self.resetHeight(height); + + if (height > self.tip.height) { + return self.blockdb.resetHeight(self.tip.height, function(err) { + if (err) + throw err; + + return doneForReal(); + }); + } + }); } (function next() { @@ -267,7 +297,7 @@ Chain.prototype._saveBlock = function _saveBlock(block, callback) { if (!this.blockdb) return callback(); - this.blockdb.block.saveBlock(block, function(err) { + this.blockdb.saveBlock(block, function(err) { if (err) return callback(err); @@ -474,6 +504,7 @@ Chain.prototype._verify = function _verify(block, prev) { }; Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callback) { + var self = this; var height = prev.height + 1; var pending = block.txs.length; var called; @@ -521,6 +552,7 @@ Chain.prototype._checkDuplicates = function _checkDuplicates(block, prev, callba }; Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callback) { + var self = this; var height = prev.height + 1; if (!this.blockdb || block.subtype !== 'block') @@ -542,7 +574,7 @@ Chain.prototype._checkInputs = function _checkInputs(block, prev, flags, callbac // Check all transactions for (i = 0; i < block.txs.length; i++) { - tx = blocks.txs[i]; + tx = block.txs[i]; hash = tx.hash('hex'); for (j = 0; j < tx.inputs.length; j++) { diff --git a/lib/bcoin/chaindb.js b/lib/bcoin/chaindb.js index 9be887f4..cfc4eaa7 100644 --- a/lib/bcoin/chaindb.js +++ b/lib/bcoin/chaindb.js @@ -85,7 +85,34 @@ ChainDB.prototype._init = function _init() { this.fd = fs.openSync(this.file, 'r+'); }; -ChainDB.prototype._malloc = function(size) { +ChainDB.prototype.closeSync = function closeSync() { + if (!bcoin.fs) { + this.ramdisk = null; + return; + } + fs.closeSync(this.fd); + this.fd = null; +}; + +ChainDB.prototype.closeAsync = function closeAsync(callback) { + var self = this; + + callback = utils.asyncify(callback); + + if (!bcoin.fs) { + this.ramdisk = null; + return callback(); + } + + fs.close(this.fd, function(err) { + if (err) + return callback(err); + self.fd = null; + return callback(); + }); +}; + +ChainDB.prototype._malloc = function _malloc(size) { if (!this._bufferPool[size]) this._bufferPool[size] = new Buffer(size); @@ -97,7 +124,7 @@ ChainDB.prototype._malloc = function(size) { return this._bufferPool[size]; }; -ChainDB.prototype._free = function(buf) { +ChainDB.prototype._free = function _free(buf) { if (this._bufferPool.used[buf.length] === buf) { assert(this._bufferPool[buf.length] === buf); delete this._bufferPool.used[buf.length]; @@ -312,22 +339,49 @@ ChainDB.prototype.remove = function remove(height) { assert(height >= 0); - this.truncate(height); + this.truncateSync(height); } return true; }; -ChainDB.prototype.truncate = function truncate(height) { - this.size = (height + 1) * BLOCK_SIZE; - this.tip = height; +ChainDB.prototype.truncateSync = function truncateSync(height) { + var size = (height + 1) * BLOCK_SIZE; if (!bcoin.fs) { - this.ramdisk.truncate(this.size); + this.ramdisk.truncate(size); + this.size = size; + this.tip = height; return; } - fs.ftruncateSync(this.fd, this.size); + fs.ftruncateSync(this.fd, size); + this.size = size; + this.tip = height; +}; + +ChainDB.prototype.truncateAsync = function truncateAsync(height) { + var self = this; + var size = (height + 1) * BLOCK_SIZE; + + callback = utils.asyncify(callback); + + if (!bcoin.fs) { + this.ramdisk.truncate(size); + this.size = size; + this.tip = height; + return callback(); + } + + fs.ftruncate(this.fd, size, function(err) { + if (err) + return callback(err); + + self.size = size; + self.tip = height; + + return callback(); + }); }; ChainDB.prototype.isNull = function isNull(height) {