chain: improve spv reorgs.
This commit is contained in:
parent
41f711987c
commit
708c4a2bd2
@ -760,7 +760,7 @@ Chain.prototype.reorganize = co(function* reorganize(competitor, block) {
|
||||
var connect = [];
|
||||
var i, entry;
|
||||
|
||||
assert(fork);
|
||||
assert(fork, 'No free space or data corruption.');
|
||||
|
||||
// Blocks to disconnect.
|
||||
entry = tip;
|
||||
@ -796,7 +796,8 @@ Chain.prototype.reorganize = co(function* reorganize(competitor, block) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Reorganize the blockchain for SPV.
|
||||
* Reorganize the blockchain for SPV. This
|
||||
* will reset the chain to the fork block.
|
||||
* @private
|
||||
* @param {ChainEntry} competitor - The competing chain's tip.
|
||||
* @param {Block|MerkleBlock} block - The being being added.
|
||||
@ -806,22 +807,32 @@ Chain.prototype.reorganize = co(function* reorganize(competitor, block) {
|
||||
Chain.prototype.reorganizeSPV = co(function* reorganizeSPV(competitor, block) {
|
||||
var tip = this.tip;
|
||||
var fork = yield this.findFork(tip, competitor);
|
||||
var entry;
|
||||
var disconnect = [];
|
||||
var entry = tip;
|
||||
var i;
|
||||
|
||||
assert(fork);
|
||||
assert(fork, 'No free space or data corruption.');
|
||||
|
||||
// Blocks to disconnect.
|
||||
entry = tip;
|
||||
// Buffer disconnected blocks.
|
||||
while (entry.hash !== fork.hash) {
|
||||
this.emit('disconnect', entry, entry.toHeaders());
|
||||
disconnect.push(entry);
|
||||
entry = yield entry.getPrevious();
|
||||
assert(entry);
|
||||
}
|
||||
|
||||
// Reset the main chain back
|
||||
// to the fork block.
|
||||
// to the fork block, causing
|
||||
// us to redownload the blocks
|
||||
// on the new main chain.
|
||||
yield this._reset(fork.hash);
|
||||
|
||||
// Emit disconnection events now that
|
||||
// the chain has successfully reset.
|
||||
for (i = 0; i < disconnect.length; i++) {
|
||||
entry = disconnect[i];
|
||||
this.emit('disconnect', entry, entry.toHeaders());
|
||||
}
|
||||
|
||||
this.emit('reorganize', block, tip.height, tip.hash);
|
||||
});
|
||||
|
||||
@ -983,46 +994,40 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
|
||||
});
|
||||
|
||||
/**
|
||||
* Reset the chain to the desired height. This
|
||||
* Reset the chain to the desired block. This
|
||||
* is useful for replaying the blockchain download
|
||||
* for SPV.
|
||||
* @param {Number} height
|
||||
* @param {Hash|Number} block
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Chain.prototype.reset = co(function* reset(height) {
|
||||
Chain.prototype.reset = co(function* reset(block) {
|
||||
var unlock = yield this.locker.lock();
|
||||
try {
|
||||
return yield this._reset(height);
|
||||
return yield this._reset(block);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Reset the chain to the desired height without a lock.
|
||||
* Reset the chain to the desired block without a lock.
|
||||
* @private
|
||||
* @param {Number} height
|
||||
* @param {Hash|Number} block
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Chain.prototype._reset = co(function* reset(height) {
|
||||
var tip = yield this.db.reset(height);
|
||||
|
||||
this.synced = false;
|
||||
Chain.prototype._reset = co(function* reset(block) {
|
||||
var tip = yield this.db.reset(block);
|
||||
|
||||
// Reset state.
|
||||
this.tip = tip;
|
||||
this.height = tip.height;
|
||||
|
||||
this.state = yield this.getDeploymentState();
|
||||
this.synced = this.isFull(true);
|
||||
|
||||
this.emit('tip', tip);
|
||||
|
||||
if (this.isFull()) {
|
||||
this.synced = true;
|
||||
this.emit('full');
|
||||
}
|
||||
|
||||
// Reset the orphan map completely. There may
|
||||
// have been some orphans on a forked chain we
|
||||
// no longer need.
|
||||
@ -1583,8 +1588,8 @@ Chain.prototype.getOrphan = function getOrphan(hash) {
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Chain.prototype.isFull = function isFull() {
|
||||
return !this.isInitial();
|
||||
Chain.prototype.isFull = function isFull(force) {
|
||||
return !this.isInitial(force);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1595,8 +1600,8 @@ Chain.prototype.isFull = function isFull() {
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
Chain.prototype.isInitial = function isInitial() {
|
||||
if (this.synced)
|
||||
Chain.prototype.isInitial = function isInitial(force) {
|
||||
if (!force && this.synced)
|
||||
return false;
|
||||
|
||||
if (this.height < this.network.checkpoints.lastHeight)
|
||||
|
||||
@ -661,7 +661,7 @@ ChainDB.prototype.getEntries = function getEntries() {
|
||||
return this.db.values({
|
||||
gte: layout.e(constants.ZERO_HASH),
|
||||
lte: layout.e(constants.MAX_HASH),
|
||||
parse: function(key, value) {
|
||||
parse: function(value) {
|
||||
return ChainEntry.fromRaw(self.chain, value);
|
||||
}
|
||||
});
|
||||
@ -1229,34 +1229,34 @@ ChainDB.prototype.save = co(function* save(entry, block, view) {
|
||||
ChainDB.prototype._save = co(function* save(entry, block, view) {
|
||||
var hash = block.hash();
|
||||
|
||||
// Hash->height index
|
||||
// Hash->height index.
|
||||
this.put(layout.h(hash), U32(entry.height));
|
||||
|
||||
// Entry data
|
||||
// Entry data.
|
||||
this.put(layout.e(hash), entry.toRaw());
|
||||
this.cacheHash.push(entry.hash, entry);
|
||||
|
||||
// Tip index
|
||||
// Tip index.
|
||||
this.del(layout.a(entry.prevBlock));
|
||||
this.put(layout.a(hash), DUMMY);
|
||||
|
||||
if (!view) {
|
||||
// Save block data
|
||||
// Save block data.
|
||||
yield this.saveBlock(block);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash->next-block index
|
||||
// Hash->next-block index.
|
||||
this.put(layout.n(entry.prevBlock), hash);
|
||||
|
||||
// Height->hash index
|
||||
// Height->hash index.
|
||||
this.put(layout.H(entry.height), hash);
|
||||
this.cacheHeight.push(entry.height, entry);
|
||||
|
||||
// Connect block and save data
|
||||
// Connect block and save data.
|
||||
yield this.saveBlock(block, view);
|
||||
|
||||
// New chain state
|
||||
// Commit new chain state.
|
||||
this.put(layout.R, this.pending.commit(hash));
|
||||
});
|
||||
|
||||
@ -1291,14 +1291,20 @@ ChainDB.prototype.reconnect = co(function* reconnect(entry, block, view) {
|
||||
ChainDB.prototype._reconnect = co(function* reconnect(entry, block, view) {
|
||||
var hash = block.hash();
|
||||
|
||||
// We can now add a hash->next-block index.
|
||||
this.put(layout.n(entry.prevBlock), hash);
|
||||
this.put(layout.H(entry.height), hash);
|
||||
|
||||
this.cacheHash.push(entry.hash, entry);
|
||||
// We can now add a height->hash index.
|
||||
this.put(layout.H(entry.height), hash);
|
||||
this.cacheHeight.push(entry.height, entry);
|
||||
|
||||
// Re-insert into cache.
|
||||
this.cacheHash.push(entry.hash, entry);
|
||||
|
||||
// Connect inputs.
|
||||
yield this.connectBlock(block, view);
|
||||
|
||||
// Update chain state.
|
||||
this.put(layout.R, this.pending.commit(hash));
|
||||
});
|
||||
|
||||
@ -1335,9 +1341,11 @@ ChainDB.prototype.disconnect = co(function* disconnect(entry) {
|
||||
ChainDB.prototype._disconnect = co(function* disconnect(entry) {
|
||||
var block;
|
||||
|
||||
// Remove hash->next-block index.
|
||||
this.del(layout.n(entry.prevBlock));
|
||||
this.del(layout.H(entry.height));
|
||||
|
||||
// Remove height->hash index.
|
||||
this.del(layout.H(entry.height));
|
||||
this.cacheHeight.unpush(entry.height);
|
||||
|
||||
block = yield this.getBlock(entry.hash);
|
||||
@ -1345,8 +1353,10 @@ ChainDB.prototype._disconnect = co(function* disconnect(entry) {
|
||||
if (!block)
|
||||
throw new Error('Block not found.');
|
||||
|
||||
// Disconnect inputs.
|
||||
yield this.disconnectBlock(block);
|
||||
|
||||
// Revert chain state to previous tip.
|
||||
this.put(layout.R, this.pending.commit(entry.prevBlock));
|
||||
|
||||
return block;
|
||||
@ -1386,6 +1396,7 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
||||
for (;;) {
|
||||
this.start();
|
||||
|
||||
// Stop once we hit our target tip.
|
||||
if (tip.hash === entry.hash) {
|
||||
this.put(layout.R, this.pending.commit(tip.hash));
|
||||
yield this.commit();
|
||||
@ -1394,14 +1405,18 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
||||
|
||||
assert(!tip.isGenesis());
|
||||
|
||||
// Revert the tip index.
|
||||
this.del(layout.a(tip.hash));
|
||||
this.put(layout.a(tip.prevBlock), DUMMY);
|
||||
|
||||
// Remove all records (including
|
||||
// main-chain-only records).
|
||||
this.del(layout.H(tip.height));
|
||||
this.del(layout.h(tip.hash));
|
||||
this.del(layout.e(tip.hash));
|
||||
this.del(layout.n(tip.prevBlock));
|
||||
|
||||
// Disconnect and remove block data.
|
||||
try {
|
||||
yield this.removeBlock(tip.hash);
|
||||
} catch (e) {
|
||||
@ -1409,10 +1424,12 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Revert chain state to previous tip.
|
||||
this.put(layout.R, this.pending.commit(tip.prevBlock));
|
||||
|
||||
yield this.commit();
|
||||
|
||||
// Update caches _after_ successful commit.
|
||||
this.cacheHeight.remove(tip.height);
|
||||
this.cacheHash.remove(tip.hash);
|
||||
|
||||
@ -1432,17 +1449,29 @@ ChainDB.prototype.removeChains = co(function* removeChains() {
|
||||
var tips = yield this.getTips();
|
||||
var i;
|
||||
|
||||
for (i = 0; i < tips.length; i++)
|
||||
yield this.removeChain(tips[i]);
|
||||
// Note that this has to be
|
||||
// one giant atomic write!
|
||||
this.start();
|
||||
|
||||
try {
|
||||
for (i = 0; i < tips.length; i++)
|
||||
yield this._removeChain(tips[i]);
|
||||
} catch (e) {
|
||||
this.drop();
|
||||
throw e;
|
||||
}
|
||||
|
||||
yield this.commit();
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove an alternate chain.
|
||||
* @private
|
||||
* @param {Hash} hash - Alternate chain tip.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ChainDB.prototype.removeChain = co(function* removeChain(hash) {
|
||||
ChainDB.prototype._removeChain = co(function* removeChain(hash) {
|
||||
var tip = yield this.get(hash);
|
||||
|
||||
if (!tip)
|
||||
@ -1450,27 +1479,25 @@ ChainDB.prototype.removeChain = co(function* removeChain(hash) {
|
||||
|
||||
this.logger.debug('Removing alternate chain: %s.', tip.rhash);
|
||||
|
||||
this.start();
|
||||
|
||||
while (tip) {
|
||||
for (;;) {
|
||||
if (yield tip.isMainChain())
|
||||
break;
|
||||
|
||||
assert(!tip.isGenesis());
|
||||
|
||||
// Remove all non-main-chain records.
|
||||
this.del(layout.a(tip.hash));
|
||||
this.del(layout.h(tip.hash));
|
||||
this.del(layout.e(tip.hash));
|
||||
this.del(layout.b(tip.hash));
|
||||
|
||||
// Queue up hash to be removed
|
||||
// on successful write.
|
||||
this.cacheHash.unpush(tip.hash);
|
||||
|
||||
tip = yield this.get(tip.prevBlock);
|
||||
assert(tip);
|
||||
}
|
||||
|
||||
yield this.commit();
|
||||
|
||||
return tip;
|
||||
});
|
||||
|
||||
/**
|
||||
@ -1485,6 +1512,8 @@ ChainDB.prototype.saveBlock = co(function* saveBlock(block, view) {
|
||||
if (this.options.spv)
|
||||
return;
|
||||
|
||||
// Write actual block data (this may be
|
||||
// better suited to flat files in the future).
|
||||
this.put(layout.b(block.hash()), block.toRaw());
|
||||
|
||||
if (!view)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user