chain: handle chain resetting more gracefully.

This commit is contained in:
Christopher Jeffrey 2016-11-14 15:45:32 -08:00
parent 6d3401f08b
commit 025a5b9138
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 81 additions and 8 deletions

View File

@ -824,7 +824,7 @@ Chain.prototype.reorganizeSPV = co(function* reorganizeSPV(competitor, 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, true);
// Emit disconnection events now that
// the chain has successfully reset.
@ -1004,7 +1004,7 @@ Chain.prototype.saveAlternate = co(function* saveAlternate(entry, block, prev) {
Chain.prototype.reset = co(function* reset(block) {
var unlock = yield this.locker.lock();
try {
return yield this._reset(block);
return yield this._reset(block, false);
} finally {
unlock();
}
@ -1017,7 +1017,7 @@ Chain.prototype.reset = co(function* reset(block) {
* @returns {Promise}
*/
Chain.prototype._reset = co(function* reset(block) {
Chain.prototype._reset = co(function* reset(block, silent) {
var tip = yield this.db.reset(block);
// Reset state.
@ -1028,6 +1028,9 @@ Chain.prototype._reset = co(function* reset(block) {
this.emit('tip', tip);
if (!silent)
this.emit('reset', tip);
// Reset the orphan map completely. There may
// have been some orphans on a forked chain we
// no longer need.
@ -1042,6 +1045,22 @@ Chain.prototype._reset = co(function* reset(block) {
*/
Chain.prototype.replay = co(function* replay(block) {
var unlock = yield this.locker.lock();
try {
return yield this._replay(block);
} finally {
unlock();
}
});
/**
* Reset the chain without a lock.
* @private
* @param {Hash|Number} block - hash/height
* @returns {Promise}
*/
Chain.prototype._replay = co(function* replay(block) {
var entry = yield this.db.get(block);
if (!entry)
@ -1050,10 +1069,27 @@ Chain.prototype.replay = co(function* replay(block) {
if (!(yield entry.isMainChain()))
throw new Error('Cannot reset on alternate chain.');
if (entry.hash === this.network.genesis.hash)
return yield this.reset(entry.hash);
if (entry.isGenesis())
return yield this._reset(entry.hash, true);
yield this.reset(entry.prevBlock);
yield this._reset(entry.prevBlock, true);
});
/**
* Scan the blockchain for transactions containing specified address hashes.
* @param {Hash} start - Block hash to start at.
* @param {Bloom} filter - Bloom filter containing tx and address hashes.
* @param {Function} iter - Iterator.
* @returns {Promise}
*/
Chain.prototype.scan = co(function* scan(start, filter, iter) {
var unlock = yield this.locker.lock();
try {
return yield this.db.scan(block, filter, iter);
} finally {
unlock();
}
});
/**
@ -1086,7 +1122,7 @@ Chain.prototype._resetTime = co(function* resetTime(ts) {
if (!entry)
return;
yield this._reset(entry.height);
yield this._reset(entry.height, false);
});
/**

View File

@ -223,6 +223,10 @@ FullNode.prototype._init = function _init() {
self.mempool.removeBlock(block).catch(onError);
});
this.chain.on('reset', function(tip) {
self.walletdb.resetChain(tip).catch(onError);
});
this.miner.on('block', function(block) {
self.broadcast(block.toInv()).catch(onError);
});
@ -296,7 +300,7 @@ FullNode.prototype.watchData = function watchData(chunks) {
*/
FullNode.prototype.scan = function scan(start, filter, iter) {
return this.chain.db.scan(start, filter, iter);
return this.chain.scan(start, filter, iter);
};
/**

View File

@ -153,6 +153,10 @@ SPVNode.prototype._init = function _init() {
this.chain.on('disconnect', function(entry, block) {
self.walletdb.removeBlock(entry).catch(onError);
});
this.chain.on('reset', function(tip) {
self.walletdb.resetChain(tip).catch(onError);
});
};
/**

View File

@ -2075,6 +2075,35 @@ WalletDB.prototype._unconfirm = co(function* unconfirm(tx) {
}
});
/**
* Handle a chain reset.
* @param {ChainEntry} entry
* @returns {Promise}
*/
WalletDB.prototype.resetChain = co(function* resetChain(entry) {
var unlock = yield this.txLock.lock();
try {
return yield this._resetChain(entry);
} finally {
unlock();
}
});
/**
* Handle a chain reset without a lock.
* @private
* @param {ChainEntry} entry
* @returns {Promise}
*/
WalletDB.prototype._resetChain = co(function* resetChain(entry) {
if (entry.height > this.state.height)
throw new Error('WDB: Bad reset height.');
yield this.scan(entry.height);
});
/*
* Helpers
*/