chaindb: refactor writes and pruning.

This commit is contained in:
Christopher Jeffrey 2016-10-04 22:34:46 -07:00
parent b5dee34f24
commit 1f790f94b9
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -40,7 +40,6 @@ var DUMMY = new Buffer([0]);
* C[addr-hash][hash][index] -> dummy (coin by address) * C[addr-hash][hash][index] -> dummy (coin by address)
* W+T[witaddr-hash][hash] -> dummy (tx by address) * W+T[witaddr-hash][hash] -> dummy (tx by address)
* W+C[witaddr-hash][hash][index] -> dummy (coin by address) * W+C[witaddr-hash][hash][index] -> dummy (coin by address)
* q[height] -> block hash to be pruned
*/ */
var layout = { var layout = {
@ -69,9 +68,6 @@ var layout = {
u: function u(hash) { u: function u(hash) {
return pair(0x75, hash); return pair(0x75, hash);
}, },
q: function q(height) {
return ipair(0x71, height);
},
T: function T(address, hash) { T: function T(address, hash) {
var len = address.length; var len = address.length;
var key; var key;
@ -228,11 +224,11 @@ ChainDB.prototype._open = co(function* open() {
yield this.db.checkVersion('V', 1); yield this.db.checkVersion('V', 1);
state = yield this.db.get(layout.R); state = yield this.getState();
if (state) { if (state) {
// Grab the chainstate if we have one. // Grab the chainstate if we have one.
this.state = ChainState.fromRaw(state); this.state = state;
} else { } else {
// Otherwise write the genesis block. // Otherwise write the genesis block.
// (We assume this database is fresh). // (We assume this database is fresh).
@ -482,7 +478,7 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) {
if (hash == null) if (hash == null)
height = -1; height = -1;
return [hash, height]; return new BlockPair(hash, height);
} }
height = yield this.getHeight(hash); height = yield this.getHeight(hash);
@ -490,7 +486,7 @@ ChainDB.prototype.getBoth = co(function* getBoth(block) {
if (height === -1) if (height === -1)
hash = null; hash = null;
return [hash, height]; return new BlockPair(hash, height);
}); });
/** /**
@ -554,26 +550,39 @@ ChainDB.prototype.get = co(function* get(hash) {
*/ */
ChainDB.prototype.save = co(function* save(entry, block, view) { ChainDB.prototype.save = co(function* save(entry, block, view) {
this.start();
try {
yield this._save(entry, block, view);
} catch (e) {
this.drop();
throw e;
}
yield this.commit();
});
/**
* Save an entry without a batch.
* @private
* @param {ChainEntry} entry
* @param {Block} block
* @param {CoinView?} view
* @returns {Promise}
*/
ChainDB.prototype._save = co(function* save(entry, block, view) {
var hash = block.hash(); var hash = block.hash();
var height = new Buffer(4); var height = new Buffer(4);
height.writeUInt32LE(entry.height, 0, true); height.writeUInt32LE(entry.height, 0, true);
this.start();
this.put(layout.h(hash), height); this.put(layout.h(hash), height);
this.put(layout.e(hash), entry.toRaw()); this.put(layout.e(hash), entry.toRaw());
this.cacheHash.set(entry.hash, entry); this.cacheHash.set(entry.hash, entry);
if (!view) { if (!view) {
try { yield this.saveBlock(block);
yield this.saveBlock(block); return;
} catch (e) {
this.drop();
throw e;
}
return yield this.commit();
} }
this.cacheHeight.set(entry.height, entry); this.cacheHeight.set(entry.height, entry);
@ -581,16 +590,9 @@ ChainDB.prototype.save = co(function* save(entry, block, view) {
this.put(layout.n(entry.prevBlock), hash); this.put(layout.n(entry.prevBlock), hash);
this.put(layout.H(entry.height), hash); this.put(layout.H(entry.height), hash);
try { yield this.saveBlock(block, view);
yield this.saveBlock(block, view);
} catch (e) {
this.drop();
throw e;
}
this.put(layout.R, this.pending.commit(hash)); this.put(layout.R, this.pending.commit(hash));
yield this.commit();
}); });
/** /**
@ -602,19 +604,50 @@ ChainDB.prototype.getTip = function getTip() {
return this.get(this.state.hash); return this.get(this.state.hash);
}; };
/**
* Retrieve the tip entry from the tip record.
* @returns {Promise} - Returns {@link ChainEntry}.
*/
ChainDB.prototype.getState = co(function* getState() {
var data = yield this.db.get(layout.R);
if (!data)
return;
return ChainState.fromRaw(data);
});
/** /**
* Reconnect the block to the chain. * Reconnect the block to the chain.
* @param {ChainEntry} entry * @param {ChainEntry} entry
* @param {Block} block * @param {Block} block
* @param {CoinView} view * @param {CoinView} view
* @returns {Promise} - * @returns {Promise}
* Returns [Error, {@link ChainEntry}, {@link Block}].
*/ */
ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) { ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) {
var hash = block.hash();
this.start(); this.start();
try {
yield this._reconnect(entry, block, view);
} catch (e) {
this.drop();
throw e;
}
yield this.commit();
});
/**
* Reconnect block without a batch.
* @private
* @param {ChainEntry} entry
* @param {Block} block
* @param {CoinView} view
* @returns {Promise}
*/
ChainDB.prototype._reconnect = co(function* reconnect(entry, block, view) {
var hash = block.hash();
this.put(layout.n(entry.prevBlock), hash); this.put(layout.n(entry.prevBlock), hash);
this.put(layout.H(entry.height), hash); this.put(layout.H(entry.height), hash);
@ -622,31 +655,46 @@ ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) {
this.cacheHash.set(entry.hash, entry); this.cacheHash.set(entry.hash, entry);
this.cacheHeight.set(entry.height, entry); this.cacheHeight.set(entry.height, entry);
try { yield this.connectBlock(block, view);
yield this.connectBlock(block, view);
} catch (e) {
this.drop();
throw e;
}
this.put(layout.R, this.pending.commit(hash)); this.put(layout.R, this.pending.commit(hash));
yield this.commit();
return block; return block;
}); });
/** /**
* Disconnect block from the chain. * Disconnect block from the chain.
* @param {ChainEntry} entry * @param {ChainEntry} entry
* @returns {Promise} - * @returns {Promise}
* Returns [Error, {@link ChainEntry}, {@link Block}].
*/ */
ChainDB.prototype.disconnect = co(function* disconnect(entry) { ChainDB.prototype.disconnect = co(function* disconnect(entry) {
var block; var block;
this.start(); this.start();
try {
block = yield this._disconnect(entry);
} catch (e) {
this.drop();
throw e;
}
yield this.commit();
return block;
});
/**
* Disconnect block without a batch.
* @private
* @param {ChainEntry} entry
* @returns {Promise}
*/
ChainDB.prototype._disconnect = co(function* disconnect(entry) {
var block;
this.del(layout.n(entry.prevBlock)); this.del(layout.n(entry.prevBlock));
this.del(layout.H(entry.height)); this.del(layout.H(entry.height));
@ -654,33 +702,18 @@ ChainDB.prototype.disconnect = co(function* disconnect(entry) {
if (this.options.spv) { if (this.options.spv) {
this.put(layout.R, this.pending.commit(entry.prevBlock)); this.put(layout.R, this.pending.commit(entry.prevBlock));
yield this.commit();
return entry.toHeaders(); return entry.toHeaders();
} }
try { block = yield this.getBlock(entry.hash);
block = yield this.getBlock(entry.hash);
} catch (e) {
this.drop();
throw e;
}
if (!block) { if (!block)
this.drop();
throw new Error('Block not found.'); throw new Error('Block not found.');
}
try { yield this.disconnectBlock(block);
yield this.disconnectBlock(block);
} catch (e) {
this.drop();
throw e;
}
this.put(layout.R, this.pending.commit(entry.prevBlock)); this.put(layout.R, this.pending.commit(entry.prevBlock));
yield this.commit();
return block; return block;
}); });
@ -782,14 +815,8 @@ ChainDB.prototype.reset = co(function* reset(block) {
*/ */
ChainDB.prototype.has = co(function* has(block) { ChainDB.prototype.has = co(function* has(block) {
var items, hash; var item = yield this.getBoth(block);
return item.hash != null;
checkHash(block);
items = yield this.getBoth(block);
hash = items[0];
return hash != null;
}); });
/** /**
@ -802,16 +829,14 @@ ChainDB.prototype.has = co(function* has(block) {
ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) { ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) {
if (this.options.spv) if (this.options.spv)
return block; return;
this.put(layout.b(block.hash()), block.toRaw()); this.put(layout.b(block.hash()), block.toRaw());
if (!view) if (!view)
return block; return;
yield this.connectBlock(block, view); yield this.connectBlock(block, view);
return block;
}); });
/** /**
@ -844,12 +869,12 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
var hashes, address, hash, coins, raw; var hashes, address, hash, coins, raw;
if (this.options.spv) if (this.options.spv)
return block; return;
// Genesis block's coinbase is unspendable. // Genesis block's coinbase is unspendable.
if (this.chain.isGenesis(block)) { if (this.chain.isGenesis(block)) {
this.pending.connect(block); this.pending.connect(block);
return block; return;
} }
this.pending.connect(block); this.pending.connect(block);
@ -927,8 +952,6 @@ ChainDB.prototype.connectBlock = co(function* connectBlock(block, view) {
this.put(layout.u(block.hash()), undo.render()); this.put(layout.u(block.hash()), undo.render());
yield this.pruneBlock(block); yield this.pruneBlock(block);
return block;
}); });
/** /**
@ -942,7 +965,7 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
var hashes, address, hash, coins, raw; var hashes, address, hash, coins, raw;
if (this.options.spv) if (this.options.spv)
return block; return;
view = yield this.getUndoView(block); view = yield this.getUndoView(block);
@ -1021,8 +1044,6 @@ ChainDB.prototype.disconnectBlock = co(function* disconnectBlock(block) {
} }
this.del(layout.u(block.hash())); this.del(layout.u(block.hash()));
return block;
}); });
/** /**
@ -1464,22 +1485,19 @@ ChainDB.prototype.getUndoView = co(function* getUndoView(block) {
*/ */
ChainDB.prototype.getBlock = co(function* getBlock(hash) { ChainDB.prototype.getBlock = co(function* getBlock(hash) {
var items = yield this.getBoth(hash); var item = yield this.getBoth(hash);
var height, data, block; var height, data, block;
hash = items[0]; if (!item.hash)
height = items[1];
if (!hash)
return; return;
data = yield this.db.get(layout.b(hash)); data = yield this.db.get(layout.b(item.hash));
if (!data) if (!data)
return; return;
block = Block.fromRaw(data); block = Block.fromRaw(data);
block.setHeight(height); block.setHeight(item.height);
return block; return block;
}); });
@ -1519,7 +1537,7 @@ ChainDB.prototype.hasCoins = function hasCoins(hash) {
*/ */
ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) { ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) {
var futureHeight, key, hash; var height, hash;
if (this.options.spv) if (this.options.spv)
return; return;
@ -1527,21 +1545,16 @@ ChainDB.prototype.pruneBlock = co(function* pruneBlock(block) {
if (!this.prune) if (!this.prune)
return; return;
if (block.height <= this.network.block.pruneAfterHeight) height = block.height - this.keepBlocks;
if (height <= this.network.block.pruneAfterHeight)
return; return;
futureHeight = block.height + this.keepBlocks; hash = yield this.getHash(height);
this.put(layout.q(futureHeight), block.hash());
key = layout.q(block.height);
hash = yield this.db.get(key);
if (!hash) if (!hash)
return; return;
this.del(key);
this.del(layout.b(hash)); this.del(layout.b(hash));
this.del(layout.u(hash)); this.del(layout.u(hash));
}); });
@ -1654,6 +1667,11 @@ function checkHash(hash) {
'Must pass in height or hash.'); 'Must pass in height or hash.');
} }
function BlockPair(hash, height) {
this.hash = hash;
this.height = height;
}
/* /*
* Expose * Expose
*/ */