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