chaindb: refactor init and deployments.
This commit is contained in:
parent
9f44ddc22f
commit
7157d06464
@ -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;
|
||||
};
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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() {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user