Skip to content

Commit f9ec3f0

Browse files
mrbandrewssdaftuar
authored andcommitted
Add block pruning functionality
This adds a -prune=N option to bitcoind, which if set to N>0 will enable block file pruning. When pruning is enabled, block and undo files will be deleted to try to keep total space used by those files to below the prune target (N, in MB) specified by the user, subject to some constraints: - The last 288 blocks on the main chain are always kept (MIN_BLOCKS_TO_KEEP), - N must be at least 550MB (chosen as a value for the target that could reasonably be met, with some assumptions about block sizes, orphan rates, etc; see comment in main.h), - No blocks are pruned until chainActive is at least 100,000 blocks long (on mainnet; defined separately for mainnet, testnet, and regtest in chainparams as nPruneAfterHeight). This unsets NODE_NETWORK if pruning is enabled. Also included is an RPC test for pruning (pruning.py). Thanks to @rdponticelli for earlier work on this feature; this is based in part off that work.
1 parent b6ea3bc commit f9ec3f0

File tree

7 files changed

+744
-37
lines changed

7 files changed

+744
-37
lines changed

qa/rpc-tests/pruning.py

Lines changed: 356 additions & 0 deletions
Large diffs are not rendered by default.

qa/rpc-tests/util.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def _rpchost_to_args(rpchost):
158158
rv += ['-rpcport=' + rpcport]
159159
return rv
160160

161-
def start_node(i, dirname, extra_args=None, rpchost=None):
161+
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None):
162162
"""
163163
Start a bitcoind and return RPC connection to it
164164
"""
@@ -172,7 +172,10 @@ def start_node(i, dirname, extra_args=None, rpchost=None):
172172
["-rpcwait", "getblockcount"], stdout=devnull)
173173
devnull.close()
174174
url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i))
175-
proxy = AuthServiceProxy(url)
175+
if timewait is not None:
176+
proxy = AuthServiceProxy(url, timeout=timewait)
177+
else:
178+
proxy = AuthServiceProxy(url)
176179
proxy.url = url # store URL on proxy for info
177180
return proxy
178181

src/chainparams.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class CMainParams : public CChainParams {
121121
vAlertPubKey = ParseHex("04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284");
122122
nDefaultPort = 8333;
123123
nMinerThreads = 0;
124+
nPruneAfterHeight = 100000;
124125

125126
/**
126127
* Build the genesis block. Note that the output of the genesis coinbase cannot
@@ -198,6 +199,7 @@ class CTestNetParams : public CMainParams {
198199
vAlertPubKey = ParseHex("04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a");
199200
nDefaultPort = 18333;
200201
nMinerThreads = 0;
202+
nPruneAfterHeight = 1000;
201203

202204
//! Modify the testnet genesis block so the timestamp is valid for a later start.
203205
genesis.nTime = 1296688602;
@@ -257,6 +259,7 @@ class CRegTestParams : public CTestNetParams {
257259
consensus.hashGenesisBlock = genesis.GetHash();
258260
nDefaultPort = 18444;
259261
assert(consensus.hashGenesisBlock == uint256S("0x0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"));
262+
nPruneAfterHeight = 1000;
260263

261264
vFixedSeeds.clear(); //! Regtest mode doesn't have any fixed seeds.
262265
vSeeds.clear(); //! Regtest mode doesn't have any DNS seeds.

src/chainparams.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class CChainParams
5858
bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; }
5959
/** Make standard checks */
6060
bool RequireStandard() const { return fRequireStandard; }
61+
int64_t PruneAfterHeight() const { return nPruneAfterHeight; }
6162
/** Make miner stop after a block is found. In RPC, don't return until nGenProcLimit blocks are generated */
6263
bool MineBlocksOnDemand() const { return fMineBlocksOnDemand; }
6364
/** In the future use NetworkIDString() for RPC fields */
@@ -77,6 +78,7 @@ class CChainParams
7778
std::vector<unsigned char> vAlertPubKey;
7879
int nDefaultPort;
7980
int nMinerThreads;
81+
uint64_t nPruneAfterHeight;
8082
std::vector<CDNSSeedData> vSeeds;
8183
std::vector<unsigned char> base58Prefixes[MAX_BASE58_TYPES];
8284
std::string strNetworkID;

src/init.cpp

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,12 @@ std::string HelpMessage(HelpMessageMode mode)
275275
#ifndef WIN32
276276
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "bitcoind.pid"));
277277
#endif
278-
strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files") + " " + _("on startup"));
278+
strUsage += HelpMessageOpt("-prune=<n>", _("Reduce storage requirements by pruning (deleting) old blocks. This mode disables wallet support and is incompatible with -txindex.") + " " +
279+
_("Warning: Reverting this setting requires re-downloading the entire blockchain.") + " " +
280+
_("(default: 0 = disable pruning blocks,") + " " +
281+
strprintf(_(">%u = target size in MiB to use for block files)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
282+
strUsage += HelpMessageOpt("-reindex", _("Rebuild block chain index from current blk000??.dat files") + " " + _("on startup"));
283+
279284
#if !defined(WIN32)
280285
strUsage += HelpMessageOpt("-sysperms", _("Create new files with system default permissions, instead of umask 077 (only effective with disabled wallet functionality)"));
281286
#endif
@@ -352,7 +357,7 @@ std::string HelpMessage(HelpMessageMode mode)
352357
strUsage += HelpMessageOpt("-flushwallet", strprintf(_("Run a thread to flush wallet periodically (default: %u)"), 1));
353358
strUsage += HelpMessageOpt("-stopafterblockimport", strprintf(_("Stop running after importing blocks from disk (default: %u)"), 0));
354359
}
355-
string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net, proxy"; // Don't translate these and qt below
360+
string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, net, proxy, prune"; // Don't translate these and qt below
356361
if (mode == HMM_BITCOIN_QT)
357362
debugCategories += ", qt";
358363
strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " +
@@ -458,10 +463,33 @@ struct CImportingNow
458463
}
459464
};
460465

466+
467+
// If we're using -prune with -reindex, then delete block files that will be ignored by the
468+
// reindex. Since reindexing works by starting at block file 0 and looping until a blockfile
469+
// is missing, and since pruning works by deleting the oldest block file first, just check
470+
// for block file 0, and if it doesn't exist, delete all the block files in the
471+
// directory (since they won't be read by the reindex but will take up disk space).
472+
void DeleteAllBlockFiles()
473+
{
474+
if (boost::filesystem::exists(GetBlockPosFilename(CDiskBlockPos(0, 0), "blk")))
475+
return;
476+
477+
LogPrintf("Removing all blk?????.dat and rev?????.dat files for -reindex with -prune\n");
478+
boost::filesystem::path blocksdir = GetDataDir() / "blocks";
479+
for (boost::filesystem::directory_iterator it(blocksdir); it != boost::filesystem::directory_iterator(); it++) {
480+
if (is_regular_file(*it)) {
481+
if ((it->path().filename().string().length() == 12) &&
482+
(it->path().filename().string().substr(8,4) == ".dat") &&
483+
((it->path().filename().string().substr(0,3) == "blk") ||
484+
(it->path().filename().string().substr(0,3) == "rev")))
485+
boost::filesystem::remove(it->path());
486+
}
487+
}
488+
}
489+
461490
void ThreadImport(std::vector<boost::filesystem::path> vImportFiles)
462491
{
463492
RenameThread("bitcoin-loadblk");
464-
465493
// -reindex
466494
if (fReindex) {
467495
CImportingNow imp;
@@ -674,6 +702,21 @@ bool AppInit2(boost::thread_group& threadGroup)
674702
if (nFD - MIN_CORE_FILEDESCRIPTORS < nMaxConnections)
675703
nMaxConnections = nFD - MIN_CORE_FILEDESCRIPTORS;
676704

705+
// if using block pruning, then disable txindex
706+
// also disable the wallet (for now, until SPV support is implemented in wallet)
707+
if (GetArg("-prune", 0)) {
708+
if (GetBoolArg("-txindex", false))
709+
return InitError(_("Prune mode is incompatible with -txindex."));
710+
#ifdef ENABLE_WALLET
711+
if (!GetBoolArg("-disablewallet", false)) {
712+
if (SoftSetBoolArg("-disablewallet", true))
713+
LogPrintf("%s : parameter interaction: -prune -> setting -disablewallet=1\n", __func__);
714+
else
715+
return InitError(_("Can't run with a wallet in prune mode."));
716+
}
717+
#endif
718+
}
719+
677720
// ********************************************************* Step 3: parameter-to-internal-flags
678721

679722
fDebug = !mapMultiArgs["-debug"].empty();
@@ -710,6 +753,21 @@ bool AppInit2(boost::thread_group& threadGroup)
710753
nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS;
711754

712755
fServer = GetBoolArg("-server", false);
756+
757+
// block pruning; get the amount of disk space (in MB) to allot for block & undo files
758+
int64_t nSignedPruneTarget = GetArg("-prune", 0) * 1024 * 1024;
759+
if (nSignedPruneTarget < 0) {
760+
return InitError(_("Prune cannot be configured with a negative value."));
761+
}
762+
nPruneTarget = (uint64_t) nSignedPruneTarget;
763+
if (nPruneTarget) {
764+
if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) {
765+
return InitError(strprintf(_("Prune configured below the minimum of %d MB. Please use a higher number."), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
766+
}
767+
LogPrintf("Prune configured to target %uMiB on disk for block and undo files.\n", nPruneTarget / 1024 / 1024);
768+
fPruneMode = true;
769+
}
770+
713771
#ifdef ENABLE_WALLET
714772
bool fDisableWallet = GetBoolArg("-disablewallet", false);
715773
#endif
@@ -1030,8 +1088,12 @@ bool AppInit2(boost::thread_group& threadGroup)
10301088
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
10311089
pcoinsTip = new CCoinsViewCache(pcoinscatcher);
10321090

1033-
if (fReindex)
1091+
if (fReindex) {
10341092
pblocktree->WriteReindexing(true);
1093+
//If we're reindexing in prune mode, wipe away all our block and undo data files
1094+
if (fPruneMode)
1095+
DeleteAllBlockFiles();
1096+
}
10351097

10361098
if (!LoadBlockIndex()) {
10371099
strLoadError = _("Error loading block database");
@@ -1055,7 +1117,18 @@ bool AppInit2(boost::thread_group& threadGroup)
10551117
break;
10561118
}
10571119

1120+
// Check for changed -prune state. What we are concerned about is a user who has pruned blocks
1121+
// in the past, but is now trying to run unpruned.
1122+
if (fHavePruned && !fPruneMode) {
1123+
strLoadError = _("You need to rebuild the database using -reindex to go back to unpruned mode. This will redownload the entire blockchain");
1124+
break;
1125+
}
1126+
10581127
uiInterface.InitMessage(_("Verifying blocks..."));
1128+
if (fHavePruned && GetArg("-checkblocks", 288) > MIN_BLOCKS_TO_KEEP) {
1129+
LogPrintf("Prune: pruned datadir may not have more than %d blocks; -checkblocks=%d may fail\n",
1130+
MIN_BLOCKS_TO_KEEP, GetArg("-checkblocks", 288));
1131+
}
10591132
if (!CVerifyDB().VerifyDB(pcoinsdbview, GetArg("-checklevel", 3),
10601133
GetArg("-checkblocks", 288))) {
10611134
strLoadError = _("Corrupted block database detected");
@@ -1106,6 +1179,15 @@ bool AppInit2(boost::thread_group& threadGroup)
11061179
mempool.ReadFeeEstimates(est_filein);
11071180
fFeeEstimatesInitialized = true;
11081181

1182+
// if prune mode, unset NODE_NETWORK and prune block files
1183+
if (fPruneMode) {
1184+
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
1185+
nLocalServices &= ~NODE_NETWORK;
1186+
if (!fReindex) {
1187+
PruneAndFlush();
1188+
}
1189+
}
1190+
11091191
// ********************************************************* Step 8: load wallet
11101192
#ifdef ENABLE_WALLET
11111193
if (fDisableWallet) {

0 commit comments

Comments
 (0)