rpc: implement pruneblockchain.
This commit is contained in:
parent
7089735fb0
commit
f0bc6d5925
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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'),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user