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.request = new utils.RequestCache();
this.loading = false;
this.tip = null;
this.height = -1;
this.mempool = options.mempool;
this.blockdb = options.blockdb;
this.resetting = false;
this.reverting = false;
this.syncing = false;
this.locked = false;
this.pending = [];
this.pendingBlocks = {};
@ -144,11 +145,6 @@ Chain.prototype._init = function _init() {
self.mempool.removeBlock(block);
});
this.db.on('tip', function(tip) {
self.tip = tip;
self.height = tip.height;
});
this.loading = true;
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) {
var self = this;
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)
return callback();
@ -681,6 +674,7 @@ Chain.prototype.resetHeight = function resetHeight(height) {
var count = this.db.count();
var i, existing;
assert(!this.resetting);
assert(height <= count - 1);
assert(this.tip);
@ -704,17 +698,17 @@ Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
var count = this.db.count();
var i, lock;
assert(!this.resetting);
assert(height <= count - 1);
assert(this.tip);
if (height === count - 1)
return utils.nextTick(callback);
lock = this.locked;
this.locked = true;
this.resetting = true;
this.db.resetHeightAsync(height, function(err) {
self.locked = lock;
self.resetting = false;
if (err)
return callback(err);
@ -735,16 +729,15 @@ Chain.prototype.resetHeightAsync = function resetHeightAsync(height, callback) {
Chain.prototype.revertHeight = function revertHeight(height, callback) {
var self = this;
var chainHeight;
var lock = this.locked;
assert(!this.locked);
assert(!this.reverting);
callback = utils.asyncify(callback);
this.locked = true;
this.reverting = true;
function done(err, result) {
self.locked = lock;
self.reverting = false;
callback(err, result);
}
@ -756,8 +749,7 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
if (!this.blockdb) {
if (height > chainHeight)
return done(new Error('Cannot reset height.'));
this.resetHeight(height);
return done();
return this.resetHeightAsync(height, done);
}
this.blockdb.getHeight(function(err, blockHeight) {
@ -780,9 +772,7 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
if (err)
return done(err);
self.resetHeight(height);
return done();
self.resetHeightAsync(height, done);
}, function(block) {
self.emit('remove block', block);
});
@ -791,32 +781,34 @@ Chain.prototype.revertHeight = function revertHeight(height, callback) {
Chain.prototype._revertLast = function _revertLast(existing, callback) {
var self = this;
return this._removeBlock(existing.hash, function(err, existingBlock) {
this._removeBlock(existing.hash, function(err, existingBlock) {
if (err)
return callback(err);
self.resetHeight(existing.height - 1);
self.resetHeightAsync(existing.height - 1, function(err) {
if (err)
return callback(err);
if (existingBlock)
self.emit('remove block', existingBlock);
if (existingBlock)
self.emit('remove block', existingBlock);
return callback();
return callback();
});
});
};
Chain.prototype.syncHeight = function syncHeight(callback) {
var self = this;
var chainHeight;
var lock = this.locked;
callback = utils.asyncify(callback);
assert(!this.locked);
assert(!this.syncing);
this.locked = true;
this.syncing = true;
function done(err, result) {
self.locked = lock;
self.syncing = false;
callback(err, result);
}
@ -842,8 +834,7 @@ Chain.prototype.syncHeight = function syncHeight(callback) {
if (blockHeight < chainHeight) {
utils.debug('BlockDB is higher than ChainDB. Syncing...');
self.resetHeight(blockHeight);
return done();
return self.resetHeightAsync(blockHeight, done);
}
if (blockHeight > chainHeight) {
@ -874,6 +865,9 @@ Chain.prototype.add = function add(initial, peer, callback) {
assert(!this.loading);
if (this.resetting || this.reverting || this.syncing)
return callback();
if (this.locked) {
this.pending.push([initial, peer, callback]);
this.pendingBlocks[initial.hash('hex')] = true;
@ -890,7 +884,7 @@ Chain.prototype.add = function add(initial, peer, callback) {
(function next(block) {
var hash = block.hash('hex');
var prevHash = block.prevBlock;
var prevHeight, entry, existing, checkpoint, prev, orphan;
var prevHeight, entry, checkpoint, prev, orphan;
// Special case for genesis block.
if (block.isGenesis())
@ -1018,52 +1012,54 @@ Chain.prototype.add = function add(initial, peer, callback) {
}
// See if the entry already exists.
existing = self.db.get(entry.height);
// 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 (self.db.has(entry.height)) {
return self.db.getAsync(entry.height, function(err, existing) {
if (err)
return done(err);
self.emit('fork', block, {
height: existing.height,
expected: existing.hash,
received: entry.hash,
checkpoint: false
}, peer);
// 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);
}
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);
// Lookup previous entry.
// We can do this synchronously:
// it is already cached.
prev = self.db.get(prevHeight);
assert(prev);
@ -1247,7 +1245,7 @@ Chain.prototype.byTime = function byTime(ts) {
};
Chain.prototype.hasBlock = function hasBlock(hash) {
return !!this.byHash(hash);
return this.db.has(hash);
};
Chain.prototype.hasOrphan = function hasOrphan(hash) {
@ -1400,49 +1398,47 @@ Chain.prototype.getLocatorAsync = function getLocatorAsync(start, callback) {
return callback(null, hashes);
}
this.db.hasAsync(top, function(err, has) {
var pending;
try {
assert(this.db.has(top));
} catch (e) {
return done(new Error('Potential reset.'));
}
if (err)
return done(err);
var pending;
if (!has)
return done(new Error('Potential reset.'));
i = top;
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;
for (;;) {
hashes.push(i);
i = i - step;
if (i <= 0) {
if (i + step !== 0)
hashes.push(0);
break;
}
if (hashes.length >= 10)
step *= 2;
pending = hashes.length;
hashes.forEach(function(height, i) {
if (typeof height === 'string') {
if (!--pending)
done();
return;
}
pending = hashes.length;
self.db.getAsync(height, function(err, existing) {
if (err)
return done(err);
hashes.forEach(function(height, i) {
if (typeof height === 'string') {
if (!--pending)
done();
return;
}
if (!existing)
return done(new Error('Potential reset.'));
self.db.getAsync(height, function(err, existing) {
if (err)
return done(err);
hashes[i] = existing.hash;
if (!existing)
return done(new Error('Potential reset.'));
hashes[i] = existing.hash;
if (!--pending)
done();
});
if (!--pending)
done();
});
});
};
@ -1467,11 +1463,11 @@ Chain.prototype.getOrphanRoot = function getOrphanRoot(hash) {
};
Chain.prototype.getHeight = function getHeight(hash) {
var entry = this.byHash(hash);
if (!entry)
var height = this.db.getHeight(hash);
if (height == null)
return -1;
return entry.height;
return height;
};
Chain.prototype.getNextBlock = function getNextBlock(hash) {

View File

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

View File

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