move more logic into chaindb.
This commit is contained in:
parent
5053fa2eeb
commit
8b17c09116
@ -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) {
|
||||
|
||||
@ -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());
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user