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 utils = require('../utils/utils');
|
||||||
var Locker = require('../utils/locker');
|
var Locker = require('../utils/locker');
|
||||||
var ChainEntry = require('./chainentry');
|
var ChainEntry = require('./chainentry');
|
||||||
|
var CoinView = require('./coinview');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var VerifyError = require('../utils/errors').VerifyError;
|
var VerifyError = require('../utils/errors').VerifyError;
|
||||||
var VerifyResult = utils.VerifyResult;
|
var VerifyResult = utils.VerifyResult;
|
||||||
@ -610,10 +611,10 @@ Chain.prototype.verifyInputs = co(function* verifyInputs(block, prev, state) {
|
|||||||
var i, view, tx, valid;
|
var i, view, tx, valid;
|
||||||
|
|
||||||
if (this.options.spv)
|
if (this.options.spv)
|
||||||
return;
|
return new CoinView();
|
||||||
|
|
||||||
if (this.isGenesis(block))
|
if (this.isGenesis(block))
|
||||||
return;
|
return new CoinView();
|
||||||
|
|
||||||
view = yield this.db.getCoinView(block);
|
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);
|
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).
|
* Disconnect an entry from the chain (updates the tip).
|
||||||
* @param {ChainEntry} entry
|
* @param {ChainEntry} entry
|
||||||
@ -829,7 +866,12 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
|
|||||||
var block = yield this.db.getBlock(entry.hash);
|
var block = yield this.db.getBlock(entry.hash);
|
||||||
var prev, view;
|
var prev, view;
|
||||||
|
|
||||||
assert(block);
|
if (this.options.spv) {
|
||||||
|
assert(!block);
|
||||||
|
block = entry.toHeaders();
|
||||||
|
} else {
|
||||||
|
assert(block);
|
||||||
|
}
|
||||||
|
|
||||||
prev = yield entry.getPrevious();
|
prev = yield entry.getPrevious();
|
||||||
assert(prev);
|
assert(prev);
|
||||||
@ -871,14 +913,18 @@ Chain.prototype.reconnect = co(function* reconnect(entry) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
|
Chain.prototype.setBestChain = co(function* setBestChain(entry, block, prev) {
|
||||||
var view;
|
var view, fork;
|
||||||
|
|
||||||
assert(this.tip);
|
|
||||||
|
|
||||||
// A higher fork has arrived.
|
// A higher fork has arrived.
|
||||||
// Time to reorganize the chain.
|
// Time to reorganize the chain.
|
||||||
if (entry.prevBlock !== this.tip.hash) {
|
if (entry.prevBlock !== this.tip.hash) {
|
||||||
this.logger.warning('WARNING: Reorganizing chain.');
|
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);
|
yield this.reorganize(entry, block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -967,16 +1013,50 @@ Chain.prototype.reset = co(function* reset(height) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
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.state = yield this.getDeploymentState();
|
||||||
|
|
||||||
|
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.
|
||||||
this.purgeOrphans();
|
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
|
* Reset the chain to the desired timestamp (within 2
|
||||||
* hours). This is useful for replaying the blockchain
|
* hours). This is useful for replaying the blockchain
|
||||||
|
|||||||
@ -1351,7 +1351,8 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
|||||||
|
|
||||||
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));
|
||||||
return yield this.commit();
|
yield this.commit();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.del(layout.H(tip.height));
|
this.del(layout.H(tip.height));
|
||||||
@ -1376,28 +1377,39 @@ ChainDB.prototype.reset = co(function* reset(block) {
|
|||||||
tip = yield this.get(tip.prevBlock);
|
tip = yield this.get(tip.prevBlock);
|
||||||
assert(tip);
|
assert(tip);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tip;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the chain to a height or hash. Useful for replaying
|
* Remove an alternate chain.
|
||||||
* the blockchain download for SPV.
|
* @param {ChainEntry} tip
|
||||||
* @param {Hash|Number} block - hash/height
|
* @param {ChainEntry} entry
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ChainDB.prototype.replay = co(function* replay(block) {
|
ChainDB.prototype.removeChain = co(function* removeChain(tip, entry) {
|
||||||
var entry = yield this.get(block);
|
this.logger.debug('Removing alternate chain: %s->%s', tip.rhash, entry.rhash);
|
||||||
|
|
||||||
if (!entry)
|
while (!tip.isGenesis()) {
|
||||||
throw new Error('Block not found.');
|
if (tip.hash === entry.hash)
|
||||||
|
break;
|
||||||
|
|
||||||
if (!(yield entry.isMainChain()))
|
this.start();
|
||||||
throw new Error('Cannot reset on alternate chain.');
|
|
||||||
|
|
||||||
if (entry.hash === this.network.genesis.hash)
|
this.del(layout.h(tip.hash));
|
||||||
return yield this.reset(entry.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) {
|
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) {
|
this.chain.on('disconnect', function(entry, block) {
|
||||||
@ -220,7 +220,7 @@ SPVNode.prototype.watchData = function watchData(chunks) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
SPVNode.prototype.scan = function rescan(start) {
|
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