rpc: implement pruneblockchain.

This commit is contained in:
Christopher Jeffrey 2017-05-14 15:24:14 -07:00
parent 7089735fb0
commit f0bc6d5925
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 155 additions and 41 deletions

View File

@ -1136,6 +1136,21 @@ Chain.prototype._invalidate = co(function* _invalidate(hash) {
this.chain.setInvalid(hash);
});
/**
* Retroactively prune the database.
* @method
* @returns {Promise}
*/
Chain.prototype.retroPrune = co(function* retroPrune() {
var unlock = yield this.locker.lock();
try {
return yield this.db.retroPrune(this.tip.hash);
} finally {
unlock();
}
});
/**
* Scan the blockchain for transactions containing specified address hashes.
* @method
@ -2346,6 +2361,7 @@ function ChainOptions(options) {
this.indexTX = false;
this.indexAddress = false;
this.forceWitness = false;
this.forcePrune = false;
this.coinCache = 0;
this.entryCache = 5000;
@ -2430,6 +2446,13 @@ ChainOptions.prototype.fromOptions = function fromOptions(options) {
this.forceWitness = options.forceWitness;
}
if (options.forcePrune != null) {
assert(typeof options.forcePrune === 'boolean');
this.forcePrune = options.forcePrune;
if (options.forcePrune)
this.prune = true;
}
if (options.coinCache != null) {
assert(util.isNumber(options.coinCache));
this.coinCache = options.coinCache;

View File

@ -91,7 +91,7 @@ ChainDB.prototype.open = co(function* open() {
if (state) {
// Verify options have not changed.
yield this.verifyFlags();
yield this.verifyFlags(state);
// Verify deployment params have not changed.
yield this.verifyDeployments();
@ -492,18 +492,64 @@ ChainDB.prototype.getFlags = co(function* getFlags() {
/**
* Verify current options against db options.
* @method
* @param {ChainState} state
* @returns {Promise}
*/
ChainDB.prototype.verifyFlags = co(function* verifyFlags() {
ChainDB.prototype.verifyFlags = co(function* verifyFlags(state) {
var options = this.options;
var flags = yield this.getFlags();
var needsWitness = false;
var needsPrune = false;
assert(flags, 'No flags found.');
if (!flags)
throw new Error('No flags found.');
flags.verify(this.options);
if (options.network !== flags.network)
throw new Error('Network mismatch for chain.');
if (this.options.forceWitness)
if (options.spv && !flags.spv)
throw new Error('Cannot retroactively enable SPV.');
if (!options.spv && flags.spv)
throw new Error('Cannot retroactively disable SPV.');
if (!flags.witness) {
if (!options.forceWitness)
throw new Error('Cannot retroactively enable witness.');
needsWitness = true;
}
if (options.prune && !flags.prune) {
if (!options.forcePrune)
throw new Error('Cannot retroactively prune.');
needsPrune = true;
}
if (!options.prune && flags.prune)
throw new Error('Cannot retroactively unprune.');
if (options.indexTX && !flags.indexTX)
throw new Error('Cannot retroactively enable TX indexing.');
if (!options.indexTX && flags.indexTX)
throw new Error('Cannot retroactively disable TX indexing.');
if (options.indexAddress && !flags.indexAddress)
throw new Error('Cannot retroactively enable address indexing.');
if (!options.indexAddress && flags.indexAddress)
throw new Error('Cannot retroactively disable address indexing.');
if (needsWitness) {
yield this.logger.info('Writing witness bit to chain flags.');
yield this.saveFlags();
}
if (needsPrune) {
yield this.logger.info('Retroactively pruning chain.');
yield this.retroPrune(state.hash());
}
});
/**
@ -653,6 +699,58 @@ ChainDB.prototype.invalidateCache = co(function* invalidateCache(bit, batch) {
}
});
/**
* Retroactively prune the database.
* @method
* @param {Hash} tip
* @returns {Promise}
*/
ChainDB.prototype.retroPrune = co(function* retroPrune(tip) {
var options = this.options;
var keepBlocks = this.network.block.keepBlocks;
var pruneAfter = this.network.block.pruneAfterHeight;
var flags = yield this.getFlags();
var height = yield this.getHeight(tip);
var i, start, end, batch, hash;
if (flags.prune)
throw new Error('Already pruned.');
if (height <= pruneAfter + keepBlocks)
return false;
batch = this.db.batch();
start = pruneAfter + 1;
end = height - keepBlocks;
for (i = start; i <= end; i++) {
hash = yield this.getHash(i);
if (!hash)
throw new Error('Cannot find hash for ' + i);
batch.del(layout.b(hash));
batch.del(layout.u(hash));
}
try {
options.prune = true;
flags = ChainFlags.fromOptions(options);
assert(flags.prune);
batch.put(layout.O, flags.toRaw());
yield batch.write();
} catch (e) {
options.prune = false;
throw e;
}
return true;
});
/**
* Get the _next_ block hash (does not work by height).
* @method
@ -1946,40 +2044,6 @@ ChainFlags.fromOptions = function fromOptions(data) {
return new ChainFlags().fromOptions(data);
};
ChainFlags.prototype.verify = function verify(options) {
if (options.network !== this.network)
throw new Error('Network mismatch for chain.');
if (options.spv && !this.spv)
throw new Error('Cannot retroactively enable SPV.');
if (!options.spv && this.spv)
throw new Error('Cannot retroactively disable SPV.');
if (!options.forceWitness) {
if (!this.witness)
throw new Error('Cannot retroactively enable witness.');
}
if (options.prune && !this.prune)
throw new Error('Cannot retroactively prune.');
if (!options.prune && this.prune)
throw new Error('Cannot retroactively unprune.');
if (options.indexTX && !this.indexTX)
throw new Error('Cannot retroactively enable TX indexing.');
if (!options.indexTX && this.indexTX)
throw new Error('Cannot retroactively disable TX indexing.');
if (options.indexAddress && !this.indexAddress)
throw new Error('Cannot retroactively enable address indexing.');
if (!options.indexAddress && this.indexAddress)
throw new Error('Cannot retroactively disable address indexing.');
};
ChainFlags.prototype.toRaw = function toRaw() {
var bw = new StaticWriter(12);
var flags = 0;

View File

@ -94,6 +94,7 @@ RPC.prototype.init = function init() {
this.add('getrawmempool', this.getRawMempool);
this.add('gettxout', this.getTXOut);
this.add('gettxoutsetinfo', this.getTXOutSetInfo);
this.add('pruneblockchain', this.pruneBlockchain);
this.add('verifychain', this.verifyChain);
this.add('invalidateblock', this.invalidateBlock);
@ -459,7 +460,12 @@ RPC.prototype.setBan = co(function* setBan(args, help) {
'setban "ip(/netmask)" "add|remove" (bantime) (absolute)');
}
addr = NetAddress.fromHostname(addr, this.network);
try {
addr = NetAddress.fromHostname(addr, this.network);
} catch (e) {
throw new RPCError(errs.CLIENT_INVALID_IP_OR_SUBNET,
'Invalid IP or subnet.');
}
switch (action) {
case 'add':
@ -571,7 +577,7 @@ RPC.prototype.getBlock = co(function* getBlock(args, help) {
if (this.chain.options.prune)
throw new RPCError(errs.MISC_ERROR, 'Block not available (pruned data)');
throw new RPCError(errs.DATABASE_ERROR, 'Can\'t read block from disk');
throw new RPCError(errs.MISC_ERROR, 'Can\'t read block from disk');
}
if (!verbose)
@ -997,6 +1003,26 @@ RPC.prototype.getTXOutSetInfo = co(function* getTXOutSetInfo(args, help) {
};
});
RPC.prototype.pruneBlockchain = co(function* pruneBlockchain(args, help) {
if (help || args.length !== 0)
throw new RPCError(errs.MISC_ERROR, 'pruneblockchain');
if (this.chain.options.spv)
throw new RPCError(errs.MISC_ERROR, 'Cannot prune chain in SPV mode.');
if (this.chain.options.prune)
throw new RPCError(errs.MISC_ERROR, 'Chain is already pruned.');
if (this.chain.height < this.network.block.pruneAfterHeight)
throw new RPCError(errs.MISC_ERROR, 'Chain is too short for pruning.');
try {
yield this.chain.retroPrune();
} catch (e) {
throw new RPCError(errs.DATABASE_ERROR, e.message);
}
});
RPC.prototype.verifyChain = co(function* verifyChain(args, help) {
var valid = new Validator([args]);
var level = valid.u32(0);

View File

@ -57,6 +57,7 @@ function FullNode(options) {
maxFiles: this.config.num('max-files'),
cacheSize: this.config.mb('cache-size'),
forceWitness: this.config.bool('force-witness'),
forcePrune: this.config.bool('force-prune'),
prune: this.config.bool('prune'),
checkpoints: this.config.bool('checkpoints'),
coinCache: this.config.mb('coin-cache'),