spv: redownload blocks after reorg. handle reset properly. always save to main chain.
This commit is contained in:
parent
b91d0cd9f1
commit
e68a64d1f9
@ -15,6 +15,7 @@ var constants = require('../protocol/constants');
|
||||
var utils = require('../utils/utils');
|
||||
var Locker = require('../utils/locker');
|
||||
var ChainEntry = require('./chainentry');
|
||||
var CoinView = require('./coinview');
|
||||
var assert = require('assert');
|
||||
var VerifyError = require('../utils/errors').VerifyError;
|
||||
var VerifyResult = utils.VerifyResult;
|
||||
@ -610,10 +611,10 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
|
||||
var i, view, tx, valid;
|
||||
|
||||
if (this.options.spv)
|
||||
return;
|
||||
return new CoinView();
|
||||
|
||||
if (this.isGenesis(block))
|
||||
return;
|
||||
return new CoinView();
|
||||
|
||||
view = yield this.db.getCoinView(block);
|
||||
|
||||
@ -794,6 +795,42 @@ Chain.prototype.reorganize = co(function* reorganize(competitor, block) {
|
||||
this.emit('reorganize', block, tip.height, tip.hash);
|
||||
});
|
||||
|
||||
/**
|
||||
* Reorganize the blockchain for SPV.
|
||||
* @private
|
||||
* @param {ChainEntry} competitor - The competing chain's tip.
|
||||
* @param {Block|MerkleBlock} block - The being being added.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Chain.prototype.reorganizeSPV = co(function* reorganizeSPV(competitor, block) {
|
||||
var tip = this.tip;
|
||||
var fork = yield this.findFork(tip, competitor);
|
||||
var entry, prev;
|
||||
|
||||
assert(fork);
|
||||
|
||||
// Blocks to disconnect.
|
||||
entry = tip;
|
||||
while (entry.hash !== fork.hash) {
|
||||
this.emit('disconnect', entry, entry.toHeaders());
|
||||
entry = yield entry.getPrevious();
|
||||
assert(entry);
|
||||
}
|
||||
|
||||
// We need to remove the alternate
|
||||
// chain to prevent further reorgs.
|
||||
prev = yield competitor.getPrevious();
|
||||
assert(prev);
|
||||
yield this.db.removeChain(prev, fork);
|
||||
|
||||
// Reset the main chain back
|
||||
// to the fork block.
|
||||
yield this._reset(fork.hash);
|
||||
|
||||
this.emit('reorganize', block, tip.height, tip.hash);
|
||||
});
|
||||
|
||||
/**
|
||||
* Disconnect an entry from the chain (updates the tip).
|
||||
* @param {ChainEntry} entry
|
||||
@ -829,7 +866,12 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
|
||||
var block = yield this.db.getBlock(entry.hash);
|
||||
var prev, view;
|
||||
|
||||
assert(block);
|
||||
if (this.options.spv) {
|
||||
assert(!block);
|
||||
block = entry.toHeaders();
|
||||
} else {
|
||||
assert(block);
|
||||
}
|
||||
|
||||
prev = yield entry.getPrevious();
|
||||
assert(prev);
|
||||
@ -871,14 +913,18 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
|
||||
*/
|
||||
|
||||
Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
|
||||
var view;
|
||||
|
||||
assert(this.tip);
|
||||
var view, fork;
|
||||
|
||||
// A higher fork has arrived.
|
||||
// Time to reorganize the chain.
|
||||
if (entry.prevBlock !== this.tip.hash) {
|
||||
this.logger.warning('WARNING: Reorganizing chain.');
|
||||
|
||||
// In spv-mode, we reset the
|
||||
// chain and redownload the blocks.
|
||||
if (this.options.spv)
|
||||
return yield this.reorganizeSPV(entry, block);
|
||||
|
||||
yield this.reorganize(entry, block);
|
||||
}
|
||||
|
||||
@ -967,16 +1013,50 @@ Chain.prototype.reset = co(function* reset(height) {
|
||||
*/
|
||||
|
||||
Chain.prototype._reset = co(function* reset(height) {
|
||||
yield this.db.reset(height);
|
||||
var tip = yield this.db.reset(height);
|
||||
|
||||
this.synced = false;
|
||||
|
||||
this.tip = tip;
|
||||
this.height = tip.height;
|
||||
|
||||
this.state = yield this.getDeploymentState();
|
||||
|
||||
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.
|
||||
this.purgeOrphans();
|
||||
});
|
||||
|
||||
/**
|
||||
* Reset the chain to a height or hash. Useful for replaying
|
||||
* the blockchain download for SPV.
|
||||
* @param {Hash|Number} block - hash/height
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
Chain.prototype.replay = co(function* replay(block) {
|
||||
var entry = yield this.db.get(block);
|
||||
|
||||
if (!entry)
|
||||
throw new Error('Block not found.');
|
||||
|
||||
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);
|
||||
|
||||
yield this.reset(entry.prevBlock);
|
||||
});
|
||||
|
||||
/**
|
||||
* Reset the chain to the desired timestamp (within 2
|
||||
* hours). This is useful for replaying the blockchain
|
||||
|
||||
@ -1351,7 +1351,8 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
||||
|
||||
if (tip.hash === entry.hash) {
|
||||
this.put(layout.R, this.pending.commit(tip.hash));
|
||||
return yield this.commit();
|
||||
yield this.commit();
|
||||
break;
|
||||
}
|
||||
|
||||
this.del(layout.H(tip.height));
|
||||
@ -1376,28 +1377,39 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
||||
tip = yield this.get(tip.prevBlock);
|
||||
assert(tip);
|
||||
}
|
||||
|
||||
return tip;
|
||||
});
|
||||
|
||||
/**
|
||||
* Reset the chain to a height or hash. Useful for replaying
|
||||
* the blockchain download for SPV.
|
||||
* @param {Hash|Number} block - hash/height
|
||||
* Remove an alternate chain.
|
||||
* @param {ChainEntry} tip
|
||||
* @param {ChainEntry} entry
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ChainDB.prototype.replay = co(function* replay(block) {
|
||||
var entry = yield this.get(block);
|
||||
ChainDB.prototype.removeChain = co(function* removeChain(tip, entry) {
|
||||
this.logger.debug('Removing alternate chain: %s->%s', tip.rhash, entry.rhash);
|
||||
|
||||
if (!entry)
|
||||
throw new Error('Block not found.');
|
||||
while (!tip.isGenesis()) {
|
||||
if (tip.hash === entry.hash)
|
||||
break;
|
||||
|
||||
if (!(yield entry.isMainChain()))
|
||||
throw new Error('Cannot reset on alternate chain.');
|
||||
this.start();
|
||||
|
||||
if (entry.hash === this.network.genesis.hash)
|
||||
return yield this.reset(entry.hash);
|
||||
this.del(layout.h(tip.hash));
|
||||
this.del(layout.e(tip.hash));
|
||||
this.del(layout.b(tip.hash));
|
||||
|
||||
yield this.reset(entry.prevBlock);
|
||||
yield this.commit();
|
||||
|
||||
this.cacheHash.remove(tip.hash);
|
||||
|
||||
tip = yield this.get(tip.prevBlock);
|
||||
assert(tip);
|
||||
}
|
||||
|
||||
return tip;
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -147,7 +147,7 @@ SPVNode.prototype._init = function _init() {
|
||||
});
|
||||
|
||||
this.chain.on('connect', function(entry, block) {
|
||||
self.walletdb.addBlock(entry, block.txs).catch(onError);
|
||||
self.walletdb.addBlock(entry, block.txs || []).catch(onError);
|
||||
});
|
||||
|
||||
this.chain.on('disconnect', function(entry, block) {
|
||||
@ -220,7 +220,7 @@ SPVNode.prototype.watchData = function watchData(chunks) {
|
||||
*/
|
||||
|
||||
SPVNode.prototype.scan = function rescan(start) {
|
||||
return this.chain.db.replay(start);
|
||||
return this.chain.replay(start);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user