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,52 +1012,54 @@ 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) {
// Entry already exists.
if (existing) {
// We already have this block. Do regular
// orphan resolution (won't do anything).
// NOTE: Wrap this in a nextTick to avoid
// a stack overflow if there are a lot of
// existing blocks.
if (existing.hash === hash) {
self.emit('exists', block, {
height: entry.height,
hash: entry.hash
}, peer);
return utils.nextTick(handleOrphans);
}
// A valid block with an already existing
// height came in, that spells fork. We
// don't store by hash so we can't compare
// chainworks. We reset the chain, find a
// new peer, and wait to see who wins.
assert(self.db.getHeight(entry.hash) == null);
// The tip has more chainwork, it is a
// higher height than the entry. This is
// not an alternate tip. Ignore it.
if (self.tip.chainwork.cmp(entry.chainwork) > 0)
return done();
// The block has equal chainwork (an
// alternate tip). Reset the chain, find
// a new peer, and wait to see who wins.
// return self.revertHeight(existing.height - 1, function(err) {
return self._revertLast(existing, function(err, existingBlock) {
if (err) if (err)
return done(err); return done(err);
self.emit('fork', block, { // We already have this block. Do regular
height: existing.height, // orphan resolution (won't do anything).
expected: existing.hash, // NOTE: Wrap this in a nextTick to avoid
received: entry.hash, // a stack overflow if there are a lot of
checkpoint: false // existing blocks.
}, peer); if (existing.hash === hash) {
self.emit('exists', block, {
height: entry.height,
hash: entry.hash
}, peer);
return utils.nextTick(handleOrphans);
}
return done(); // A valid block with an already existing
// height came in, that spells fork. We
// don't store by hash so we can't compare
// chainworks. We reset the chain, find a
// new peer, and wait to see who wins.
assert(self.db.getHeight(entry.hash) == null);
// The tip has more chainwork, it is a
// higher height than the entry. This is
// not an alternate tip. Ignore it.
if (self.tip.chainwork.cmp(entry.chainwork) > 0)
return done();
// The block has equal chainwork (an
// alternate tip). Reset the chain, find
// a new peer, and wait to see who wins.
// return self.revertHeight(existing.height - 1, function(err) {
return self._revertLast(existing, function(err, existingBlock) {
if (err)
return done(err);
self.emit('fork', block, {
height: existing.height,
expected: existing.hash,
received: entry.hash,
checkpoint: false
}, peer);
return done();
});
}); });
} }
@ -1071,6 +1067,8 @@ Chain.prototype.add = function add(initial, peer, callback) {
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,49 +1398,47 @@ 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) {
return done(new Error('Potential reset.'));
}
if (err) var pending;
return done(err);
if (!has) i = top;
return done(new Error('Potential reset.')); for (;;) {
hashes.push(i);
i = i - step;
if (i <= 0) {
if (i + step !== 0)
hashes.push(0);
break;
}
if (hashes.length >= 10)
step *= 2;
}
i = top; pending = hashes.length;
for (;;) {
hashes.push(i); hashes.forEach(function(height, i) {
i = i - step; if (typeof height === 'string') {
if (i <= 0) { if (!--pending)
if (i + step !== 0) done();
hashes.push(0); return;
break;
}
if (hashes.length >= 10)
step *= 2;
} }
pending = hashes.length; self.db.getAsync(height, function(err, existing) {
if (err)
return done(err);
hashes.forEach(function(height, i) { if (!existing)
if (typeof height === 'string') { return done(new Error('Potential reset.'));
if (!--pending)
done();
return;
}
self.db.getAsync(height, function(err, existing) { hashes[i] = existing.hash;
if (err)
return done(err);
if (!existing) if (!--pending)
return done(new Error('Potential reset.')); done();
hashes[i] = existing.hash;
if (!--pending)
done();
});
}); });
}); });
}; };
@ -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,19 +445,10 @@ 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; fs.ftruncateSync(this.fd, 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);
this.size = size; this.size = size;
this.highest = height; this.highest = height;
@ -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;