chaindb: refactor init and deployments.

This commit is contained in:
Christopher Jeffrey 2016-11-30 17:34:22 -08:00
parent 9f44ddc22f
commit 7157d06464
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
3 changed files with 216 additions and 184 deletions

View File

@ -113,48 +113,39 @@ ChainDB.layout = layout;
*/
ChainDB.prototype._open = co(function* open() {
var state, options, block, entry;
var state;
this.logger.info('Starting chain load.');
this.logger.info('Opening ChainDB...');
yield this.db.open();
yield this.db.checkVersion('V', 2);
state = yield this.getState();
options = yield this.getOptions();
if (options) {
// Verify the options haven't changed.
this.options.verify(options);
if (this.options.forceWitness)
yield this.saveOptions();
} else {
yield this.saveOptions();
}
// Verify deployment params have not changed.
yield this.verifyDeployments();
if (state) {
// Grab the chainstate if we have one.
this.state = state;
// Verify options have not changed.
yield this.verifyOptions();
// Verify deployment params have not changed.
yield this.verifyDeployments();
// Load state caches.
this.stateCache = yield this.getStateCache();
// Grab the chainstate if we have one.
this.state = state;
this.logger.info('ChainDB successfully loaded.');
} else {
// Otherwise write the genesis block.
// (We assume this database is fresh).
block = Block.fromRaw(this.network.genesisBlock, 'hex');
block.setHeight(0);
entry = ChainEntry.fromBlock(this.chain, block);
// Database is fresh.
// Write initial state.
yield this.saveOptions();
yield this.saveDeployments();
yield this.saveGenesis();
this.logger.info('Writing genesis block to ChainDB.');
yield this.save(entry, block, new CoinView());
this.logger.info('ChainDB successfully initialized.');
}
this.logger.info('Chain successfully loaded.');
this.logger.info(
'Chain State: hash=%s tx=%d coin=%d value=%s.',
this.state.rhash,
@ -526,6 +517,25 @@ ChainDB.prototype.getState = co(function* getState() {
return ChainState.fromRaw(data);
});
/**
* Write genesis block to database.
* @returns {Promise}
*/
ChainDB.prototype.saveGenesis = co(function* saveGenesis() {
var genesis = this.network.genesisBlock;
var block = Block.fromRaw(genesis, 'hex');
var entry;
block.setHeight(0);
entry = ChainEntry.fromBlock(this.chain, block);
this.logger.info('Writing genesis block to ChainDB.');
yield this.save(entry, block, new CoinView());
});
/**
* Retrieve the tip entry from the tip record.
* @returns {Promise} - Returns {@link ChainOptions}.
@ -540,6 +550,165 @@ ChainDB.prototype.getOptions = co(function* getOptions() {
return ChainOptions.fromRaw(data);
});
/**
* Verify current options against db options.
* @returns {Promise}
*/
ChainDB.prototype.verifyOptions = co(function* verifyOptions() {
var options = yield this.getOptions();
assert(options, 'No options found.');
this.options.verify(options);
if (this.options.forceWitness)
yield this.saveOptions();
});
/**
* Get state caches.
* @returns {Promise} - Returns {@link StateCache}.
*/
ChainDB.prototype.getStateCache = co(function* getStateCache() {
var stateCache = new StateCache(this.network);
var i, items, item, key, bit, hash, state;
items = yield this.db.range({
gte: layout.v(0, constants.ZERO_HASH),
lte: layout.v(255, constants.MAX_HASH),
values: true
});
for (i = 0; i < items.length; i++) {
item = items[i];
key = layout.vv(item.key);
bit = key[0];
hash = key[1];
state = item.value[0];
stateCache.insert(bit, hash, state);
}
return stateCache;
});
/**
* Save deployment table.
* @returns {Promise}
*/
ChainDB.prototype.saveDeployments = function saveDeployments() {
var batch = this.db.batch();
this.writeDeployments(batch);
return batch.write();
};
/**
* Save deployment table.
* @returns {Promise}
*/
ChainDB.prototype.writeDeployments = function writeDeployments(batch) {
var bw = new BufferWriter();
var i, deployment;
bw.writeU8(this.network.deploys.length);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
bw.writeU8(deployment.bit);
bw.writeU32(deployment.startTime);
bw.writeU32(deployment.timeout);
}
batch.put(layout.V, bw.render());
};
/**
* Check for outdated deployments.
* @private
* @returns {Promise}
*/
ChainDB.prototype.checkDeployments = co(function* checkDeployments() {
var raw = yield this.db.get(layout.V);
var invalid = [];
var i, br, count, deployment;
var bit, start, timeout;
assert(raw, 'No deployment table found.');
br = new BufferReader(raw);
count = br.readU8();
for (i = 0; i < count; i++) {
bit = br.readU8();
start = br.readU32();
timeout = br.readU32();
deployment = this.network.byBit(bit);
if (deployment
&& start === deployment.startTime
&& timeout === deployment.timeout) {
continue;
}
invalid.push(bit);
}
return invalid;
});
/**
* Potentially invalidate state cache.
* @returns {Promise}
*/
ChainDB.prototype.verifyDeployments = co(function* verifyDeployments() {
var invalid = yield this.checkDeployments();
var i, bit, batch;
if (invalid.length === 0)
return true;
batch = this.db.batch();
for (i = 0; i < invalid.length; i++) {
bit = invalid[i];
this.logger.warning('Versionbit deployment params modified.');
this.logger.warning('Invalidating cache for bit %d.', bit);
yield this.invalidateCache(bit, batch);
}
this.writeDeployments(batch);
yield batch.write();
return false;
});
/**
* Invalidate state cache.
* @private
* @returns {Promise}
*/
ChainDB.prototype.invalidateCache = co(function* invalidateCache(bit, batch) {
var i, keys, key;
keys = yield this.db.keys({
gte: layout.v(bit, constants.ZERO_HASH),
lte: layout.v(bit, constants.MAX_HASH)
});
for (i = 0; i < keys.length; i++) {
key = keys[i];
batch.del(key);
}
});
/**
* Get the _next_ block hash (does not work by height).
* @param {Hash} hash
@ -740,6 +909,7 @@ ChainDB.prototype.getUndoView = co(function* getUndoView(block) {
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
input.coin = undo.apply(index++, view, input.prevout);
assert(input.coin);
}
}
@ -811,86 +981,6 @@ ChainDB.prototype.getFullBlock = co(function* getFullBlock(hash) {
return block;
});
/**
* Get state caches.
* @returns {Promise} - Returns {@link StateCache}.
*/
ChainDB.prototype.getStateCache = co(function* getStateCache() {
var stateCache = new StateCache(this.network);
var i, items, item;
items = yield this.db.range({
gte: layout.v(0, constants.ZERO_HASH),
lte: layout.v(255, constants.MAX_HASH),
values: true
});
for (i = 0; i < items.length; i++) {
item = items[i];
stateCache.setRaw(item.key, item.value);
}
return stateCache;
});
/**
* Invalidate state cache.
* @private
* @returns {Promise}
*/
ChainDB.prototype.invalidateCache = co(function* invalidateCache(bit, batch) {
var i, keys, key;
keys = yield this.db.keys({
gte: layout.v(bit, constants.ZERO_HASH),
lte: layout.v(bit, constants.MAX_HASH)
});
for (i = 0; i < keys.length; i++) {
key = keys[i];
batch.del(key);
}
});
/**
* Potentially invalidate state cache.
* @returns {Promise}
*/
ChainDB.prototype.verifyDeployments = co(function* verifyDeployments() {
var expected = this.stateCache.toDeployments();
var current = yield this.db.get(layout.V);
var i, invalid, bit, batch;
if (!current) {
this.logger.info('Writing deployment params to ChainDB.');
yield this.db.put(layout.V, expected);
return true;
}
invalid = this.stateCache.verifyDeployments(current);
if (invalid.length === 0)
return true;
batch = this.db.batch();
for (i = 0; i < invalid.length; i++) {
bit = invalid[i];
this.logger.warning('Versionbit deployment params modified.');
this.logger.warning('Invalidating cache for bit %d.', bit);
yield this.invalidateCache(bit, batch);
}
batch.put(layout.V, expected);
yield batch.write();
return false;
});
/**
* Fill a transaction with coins (only unspents).
* @param {TX} tx
@ -1740,6 +1830,9 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
}
}
// Remove undo coins.
this.del(layout.u(block.hash()));
// Commit new coin state.
view = view.toArray();
@ -1754,8 +1847,6 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
this.coinCache.push(coins.hash, raw);
}
}
this.del(layout.u(block.hash()));
});
/**
@ -2026,7 +2117,7 @@ ChainState.fromRaw = function fromRaw(data) {
function StateCache(network) {
this.network = network;
this.cache = [];
this.bits = [];
this.updates = [];
this._init();
}
@ -2035,59 +2126,17 @@ StateCache.prototype._init = function _init() {
var i, deployment;
for (i = 0; i < 32; i++)
this.cache.push(null);
this.bits.push(null);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
assert(!this.cache[deployment.bit]);
this.cache[deployment.bit] = {};
assert(!this.bits[deployment.bit]);
this.bits[deployment.bit] = {};
}
};
StateCache.prototype.toDeployments = function toDeployments() {
var bw = new BufferWriter();
var i, deployment;
bw.writeU8(this.network.deploys.length);
for (i = 0; i < this.network.deploys.length; i++) {
deployment = this.network.deploys[i];
bw.writeU8(deployment.bit);
bw.writeU32(deployment.startTime);
bw.writeU32(deployment.timeout);
}
return bw.render();
};
StateCache.prototype.verifyDeployments = function verifyDeployments(raw) {
var br = new BufferReader(raw);
var invalid = [];
var i, count, deployment;
var bit, start, timeout;
count = br.readU8();
for (i = 0; i < count; i++) {
bit = br.readU8();
start = br.readU32();
timeout = br.readU32();
deployment = this.network.byBit(bit);
if (deployment
&& start === deployment.startTime
&& timeout === deployment.timeout) {
continue;
}
invalid.push(bit);
}
return invalid;
};
StateCache.prototype.set = function set(bit, entry, state) {
var cache = this.cache[bit];
var cache = this.bits[bit];
assert(cache);
@ -2098,7 +2147,7 @@ StateCache.prototype.set = function set(bit, entry, state) {
};
StateCache.prototype.get = function get(bit, entry) {
var cache = this.cache[bit];
var cache = this.bits[bit];
var state;
assert(cache);
@ -2120,7 +2169,7 @@ StateCache.prototype.drop = function drop() {
for (i = 0; i < this.updates.length; i++) {
update = this.updates[i];
cache = this.cache[update.bit];
cache = this.bits[update.bit];
assert(cache);
delete cache[update.hash];
}
@ -2128,12 +2177,8 @@ StateCache.prototype.drop = function drop() {
this.updates.length = 0;
};
StateCache.prototype.setRaw = function setRaw(key, value) {
var pair = layout.vv(key);
var bit = pair[0];
var hash = pair[1];
var state = value[0];
var cache = this.cache[bit];
StateCache.prototype.insert = function insert(bit, hash, state) {
var cache = this.bits[bit];
assert(cache);
cache[hash] = state;
};

View File

@ -65,23 +65,23 @@ var updateVersion = co(function* updateVersion() {
});
var checkTipIndex = co(function* checkTipIndex() {
var iter, item;
iter = db.iterator({
var keys = yield db.keys({
gte: pair('p', constants.ZERO_HASH),
lte: pair('p', constants.MAX_HASH)
});
item = yield iter.next();
if (!item) {
if (keys.length === 0) {
console.log('No tip index found.');
console.log('Please run migrate/ensure-tip-index.js first!');
process.exit(1);
return;
}
yield iter.end();
if (keys.length < 3) {
console.log('Note: please run ensure-tip-index.js if you haven\'t yet.');
yield co.timeout(2000);
return;
}
});
var updateOptions = co(function* updateOptions() {

View File

@ -88,18 +88,6 @@ var isMainChain = co(function* isMainChain(entry, tip) {
return false;
});
var removeOld = co(function* removeOld() {
var i, keys, key;
keys = yield db.keys({
gte: pair('a', constants.ZERO_HASH),
lte: pair('a', constants.MAX_HASH)
});
for (i = 0; i < keys.length; i++)
batch.del(keys[i]);
});
// And this insane function is why we should
// be indexing tips in the first place!
var indexTips = co(function* indexTips() {
@ -163,7 +151,6 @@ co.spawn(function* () {
console.log('Opened %s.', file);
batch = db.batch();
yield checkVersion();
yield removeOld();
yield indexTips();
yield batch.write();
}).then(function() {