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