move more logic into chaindb.

This commit is contained in:
Christopher Jeffrey 2016-02-18 22:52:51 -08:00
parent 5053fa2eeb
commit 8b17c09116
3 changed files with 161 additions and 148 deletions

View File

@ -37,10 +37,11 @@ function Chain(options) {
this.db = new bcoin.chaindb(this); this.db = new bcoin.chaindb(this);
this.request = new utils.RequestCache(); this.request = new utils.RequestCache();
this.loading = false; this.loading = false;
this.tip = null;
this.height = -1;
this.mempool = options.mempool; this.mempool = options.mempool;
this.blockdb = options.blockdb; this.blockdb = options.blockdb;
this.resetting = false;
this.reverting = false;
this.syncing = false;
this.locked = false; this.locked = false;
this.pending = []; this.pending = [];
this.pendingBlocks = {}; this.pendingBlocks = {};
@ -144,11 +145,6 @@ Chain.prototype._init = function _init() {
self.mempool.removeBlock(block); self.mempool.removeBlock(block);
}); });
this.db.on('tip', function(tip) {
self.tip = tip;
self.height = tip.height;
});
this.loading = true; this.loading = true;
utils.debug('Chain is loading.'); utils.debug('Chain is loading.');
@ -179,22 +175,19 @@ Chain.prototype._init = function _init() {
}); });
}; };
Chain.prototype.__defineGetter__('tip', function() {
return this.db.tip;
});
Chain.prototype.__defineGetter__('height', function() {
return this.db.height;
});
Chain.prototype._ensureGenesis = function _ensureGenesis(callback) { Chain.prototype._ensureGenesis = function _ensureGenesis(callback) {
var self = this; var self = this;
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
this.db.save(bcoin.chainblock.fromJSON(this, {
hash: network.genesis.hash,
version: network.genesis.version,
prevBlock: network.genesis.prevBlock,
merkleRoot: network.genesis.merkleRoot,
ts: network.genesis.ts,
bits: network.genesis.bits,
nonce: network.genesis.nonce,
height: 0
}));
if (!this.blockdb) if (!this.blockdb)
return callback(); return callback();
@ -681,6 +674,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
var count = this.db.count(); var count = this.db.count();
var i, existing; var i, existing;
assert(!this.resetting);
assert(height <= count - 1); assert(height <= count - 1);
assert(this.tip); assert(this.tip);
@ -704,17 +698,17 @@ Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
var count = this.db.count(); var count = this.db.count();
var i, lock; var i, lock;
assert(!this.resetting);
assert(height <= count - 1); assert(height <= count - 1);
assert(this.tip); assert(this.tip);
if (height === count - 1) if (height === count - 1)
return utils.nextTick(callback); return utils.nextTick(callback);
lock = this.locked; this.resetting = true;
this.locked = true;
this.db.resetHeightAsync(height, function(err) { this.db.resetHeightAsync(height, function(err) {
self.locked = lock; self.resetting = false;
if (err) if (err)
return callback(err); return callback(err);
@ -735,16 +729,15 @@ Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
Chain.prototype.revertHeight = function revertHeight(height, callback) { Chain.prototype.revertHeight = function revertHeight(height, callback) {
var self = this; var self = this;
var chainHeight; var chainHeight;
var lock = this.locked;
assert(!this.locked); assert(!this.reverting);
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
this.locked = true; this.reverting = true;
function done(err, result) { function done(err, result) {
self.locked = lock; self.reverting = false;
callback(err, result); callback(err, result);
} }
@ -756,8 +749,7 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
if (!this.blockdb) { if (!this.blockdb) {
if (height > chainHeight) if (height > chainHeight)
return done(new Error('Cannot reset height.')); return done(new Error('Cannot reset height.'));
this.resetHeight(height); return this.resetHeightAsync(height, done);
return done();
} }
this.blockdb.getHeight(function(err, blockHeight) { this.blockdb.getHeight(function(err, blockHeight) {
@ -780,9 +772,7 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
if (err) if (err)
return done(err); return done(err);
self.resetHeight(height); self.resetHeightAsync(height, done);
return done();
}, function(block) { }, function(block) {
self.emit('remove block', block); self.emit('remove block', block);
}); });
@ -791,32 +781,34 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
Chain.prototype._revertLast = function _revertLast(existing, callback) { Chain.prototype._revertLast = function _revertLast(existing, callback) {
var self = this; var self = this;
return this._removeBlock(existing.hash, function(err, existingBlock) { this._removeBlock(existing.hash, function(err, existingBlock) {
if (err) if (err)
return callback(err); return callback(err);
self.resetHeight(existing.height - 1); self.resetHeightAsync(existing.height - 1, function(err) {
if (err)
return callback(err);
if (existingBlock) if (existingBlock)
self.emit('remove block', existingBlock); self.emit('remove block', existingBlock);
return callback(); return callback();
}); });
});
}; };
Chain.prototype.syncHeight = function syncHeight(callback) { Chain.prototype.syncHeight = function syncHeight(callback) {
var self = this; var self = this;
var chainHeight; var chainHeight;
var lock = this.locked;
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
assert(!this.locked); assert(!this.syncing);
this.locked = true; this.syncing = true;
function done(err, result) { function done(err, result) {
self.locked = lock; self.syncing = false;
callback(err, result); callback(err, result);
} }
@ -842,8 +834,7 @@ Chain.prototype.syncHeight = function syncHeight(callback) {
if (blockHeight < chainHeight) { if (blockHeight < chainHeight) {
utils.debug('BlockDB is higher than ChainDB. Syncing...'); utils.debug('BlockDB is higher than ChainDB. Syncing...');
self.resetHeight(blockHeight); return self.resetHeightAsync(blockHeight, done);
return done();
} }
if (blockHeight > chainHeight) { if (blockHeight > chainHeight) {
@ -874,6 +865,9 @@ Chain.prototype.add = function add(initial, peer, callback) {
assert(!this.loading); assert(!this.loading);
if (this.resetting || this.reverting || this.syncing)
return callback();
if (this.locked) { if (this.locked) {
this.pending.push([initial, peer, callback]); this.pending.push([initial, peer, callback]);
this.pendingBlocks[initial.hash('hex')] = true; this.pendingBlocks[initial.hash('hex')] = true;
@ -890,7 +884,7 @@ Chain.prototype.add = function add(initial, peer, callback) {
(function next(block) { (function next(block) {
var hash = block.hash('hex'); var hash = block.hash('hex');
var prevHash = block.prevBlock; var prevHash = block.prevBlock;
var prevHeight, entry, existing, checkpoint, prev, orphan; var prevHeight, entry, checkpoint, prev, orphan;
// Special case for genesis block. // Special case for genesis block.
if (block.isGenesis()) if (block.isGenesis())
@ -1018,10 +1012,11 @@ Chain.prototype.add = function add(initial, peer, callback) {
} }
// See if the entry already exists. // See if the entry already exists.
existing = self.db.get(entry.height); if (self.db.has(entry.height)) {
return self.db.getAsync(entry.height, function(err, existing) {
if (err)
return done(err);
// Entry already exists.
if (existing) {
// We already have this block. Do regular // We already have this block. Do regular
// orphan resolution (won't do anything). // orphan resolution (won't do anything).
// NOTE: Wrap this in a nextTick to avoid // NOTE: Wrap this in a nextTick to avoid
@ -1065,12 +1060,15 @@ Chain.prototype.add = function add(initial, peer, callback) {
return done(); return done();
}); });
});
} }
// Add entry if we do not have it. // Add entry if we do not have it.
assert(self.db.getHeight(entry.hash) == null); assert(self.db.getHeight(entry.hash) == null);
// Lookup previous entry. // Lookup previous entry.
// We can do this synchronously:
// it is already cached.
prev = self.db.get(prevHeight); prev = self.db.get(prevHeight);
assert(prev); assert(prev);
@ -1247,7 +1245,7 @@ Chain.prototype.byTime = function byTime(ts) {
}; };
Chain.prototype.hasBlock = function hasBlock(hash) { Chain.prototype.hasBlock = function hasBlock(hash) {
return !!this.byHash(hash); return this.db.has(hash);
}; };
Chain.prototype.hasOrphan = function hasOrphan(hash) { Chain.prototype.hasOrphan = function hasOrphan(hash) {
@ -1400,14 +1398,13 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
return callback(null, hashes); return callback(null, hashes);
} }
this.db.hasAsync(top, function(err, has) { try {
var pending; assert(this.db.has(top));
} catch (e) {
if (err)
return done(err);
if (!has)
return done(new Error('Potential reset.')); return done(new Error('Potential reset.'));
}
var pending;
i = top; i = top;
for (;;) { for (;;) {
@ -1444,7 +1441,6 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
done(); done();
}); });
}); });
});
}; };
Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) { Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
@ -1467,11 +1463,11 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
}; };
Chain.prototype.getHeight = function getHeight(hash) { Chain.prototype.getHeight = function getHeight(hash) {
var entry = this.byHash(hash); var height = this.db.getHeight(hash);
if (!entry) if (height == null)
return -1; return -1;
return entry.height; return height;
}; };
Chain.prototype.getNextBlock = function getNextBlock(hash) { Chain.prototype.getNextBlock = function getNextBlock(hash) {

View File

@ -53,6 +53,8 @@ ChainBlock.prototype.getProof = function getProof() {
}; };
ChainBlock.prototype.getChainwork = function getChainwork() { ChainBlock.prototype.getChainwork = function getChainwork() {
if (!this.chain.db)
return;
var prev = this.prev; var prev = this.prev;
return (prev ? prev.chainwork : new bn(0)).add(this.getProof()); return (prev ? prev.chainwork : new bn(0)).add(this.getProof());
}; };

View File

@ -61,9 +61,21 @@ function ChainDB(chain, options) {
inherits(ChainDB, EventEmitter); inherits(ChainDB, EventEmitter);
ChainDB.prototype._init = function _init() { ChainDB.prototype._init = function _init() {
var genesis = bcoin.chainblock.fromJSON(this.chain, {
hash: network.genesis.hash,
version: network.genesis.version,
prevBlock: network.genesis.prevBlock,
merkleRoot: network.genesis.merkleRoot,
ts: network.genesis.ts,
bits: network.genesis.bits,
nonce: network.genesis.nonce,
height: 0
});
if (!bcoin.fs) { if (!bcoin.fs) {
utils.debug('`fs` module not available. Falling back to ramdisk.'); utils.debug('`fs` module not available. Falling back to ramdisk.');
this.ramdisk = new bcoin.ramdisk(40 * 1024 * 1024); this.ramdisk = new bcoin.ramdisk(40 * 1024 * 1024);
this.saveSync(genesis);
return; return;
} }
@ -88,6 +100,13 @@ ChainDB.prototype._init = function _init() {
} }
this.fd = fs.openSync(this.file, 'r+'); this.fd = fs.openSync(this.file, 'r+');
if (this.size === 0) {
this.saveSync(genesis);
} else {
this.getSync(0);
assert(this.tip.hash === genesis.hash);
}
}; };
ChainDB.prototype.load = function load(start, callback) { ChainDB.prototype.load = function load(start, callback) {
@ -403,15 +422,19 @@ ChainDB.prototype.drop = function drop(height) {
}; };
ChainDB.prototype.resetHeight = function resetHeight(height) { ChainDB.prototype.resetHeight = function resetHeight(height) {
var size, count, existing;
if (typeof height === 'string') if (typeof height === 'string')
height = this.heightLookup[height]; height = this.heightLookup[height];
assert(height >= 0); assert(height >= 0);
var size = (height + 1) * BLOCK_SIZE; size = (height + 1) * BLOCK_SIZE;
var count = this.count(); count = this.count();
if (height === count - 1) if (height === count - 1)
return; return;
assert(height <= count - 1); assert(height <= count - 1);
assert(this.tip); assert(this.tip);
@ -422,18 +445,9 @@ ChainDB.prototype.resetHeight = function resetHeight(height) {
delete this.heightLookup[existing.hash]; delete this.heightLookup[existing.hash];
} }
if (!bcoin.fs) { if (!bcoin.fs)
this.ramdisk.truncate(size); this.ramdisk.truncate(size);
else
this.size = size;
this.highest = height;
this.tip = this.get(height);
assert(this.tip);
this.height = this.tip.height;
this.emit('tip', this.tip);
return;
}
fs.ftruncateSync(this.fd, size); fs.ftruncateSync(this.fd, size);
this.size = size; this.size = size;
@ -446,7 +460,7 @@ ChainDB.prototype.resetHeight = function resetHeight(height) {
ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback) { ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
var self = this; var self = this;
var called; var size, count, pending, called;
if (typeof height === 'string') if (typeof height === 'string')
height = this.heightLookup[height]; height = this.heightLookup[height];
@ -455,15 +469,16 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback)
callback = utils.asyncify(callback); callback = utils.asyncify(callback);
var size = (height + 1) * BLOCK_SIZE; size = (height + 1) * BLOCK_SIZE;
var count = this.count() - 1; count = this.count() - 1;
pending = count - (height + 1);
if (height === count - 1) if (height === count - 1)
return callback(); return callback();
assert(height <= count - 1); assert(height <= count - 1);
assert(this.tip); assert(this.tip);
var pending = count - (height + 1);
for (i = height + 1; i < count; i++) for (i = height + 1; i < count; i++)
dropEntry(i); dropEntry(i);
@ -491,12 +506,6 @@ ChainDB.prototype.resetHeightAsync = function resetHeightAsync(height, callback)
if (!bcoin.fs) { if (!bcoin.fs) {
self.ramdisk.truncate(size); self.ramdisk.truncate(size);
self.size = size;
self.highest = height;
self.tip = self.get(height);
assert(self.tip);
self.height = self.tip.height;
self.emit('tip', self.tip);
return callback(); return callback();
} }
@ -583,6 +592,9 @@ ChainDB.prototype._readAsync = function _readAsync(size, offset, callback) {
return callback(err); return callback(err);
} }
if (!bytes)
throw new Error('_readAsync() failed.');
index += bytes; index += bytes;
size -= bytes; size -= bytes;
offset += bytes; offset += bytes;
@ -655,6 +667,9 @@ ChainDB.prototype._writeAsync = function _writeAsync(data, offset, callback) {
return callback(err); return callback(err);
} }
if (!bytes)
throw new Error('_writeAsync() failed.');
index += bytes; index += bytes;
size -= bytes; size -= bytes;
offset += bytes; offset += bytes;