Merge #12653: Allow to optional specify the directory for the blocks storage

a192636 -blocksdir: keep blockindex leveldb database in datadir (Jonas Schnelli)
f38e4fd QA: Add -blocksdir test (Jonas Schnelli)
386a6b6 Allow to optional specify the directory for the blocks storage (Jonas Schnelli)

Pull request description:

  Since the actual block files taking up more and more space, it may be desirable to have them stored in a different location then the data directory (use case: SSD for chainstate, etc., HD for blocks).

  This PR adds a `-blocksdir` option that allows one to keep the blockfiles and the blockindex external from the data directory (instead of creating symlinks).

  I fist had an option to keep the blockindex within the datadir, but seems to make no sense since accessing the index will (always) lead to access (r/w) the block files.

Tree-SHA512: f8b9e1a681679eac25076dc30e45e6e12d4b2d9ac4be907cbea928a75af081dbcb0f1dd3e97169ab975f73d0bd15824c00c2a34638f3b284b39017171fce2409
This commit is contained in:
Wladimir J. van der Laan 2018-03-27 21:04:22 +02:00
commit 534b8fa560
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
9 changed files with 85 additions and 11 deletions

View File

@ -333,6 +333,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-version", _("Print version and exit")); strUsage += HelpMessageOpt("-version", _("Print version and exit"));
strUsage += HelpMessageOpt("-alertnotify=<cmd>", _("Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)")); strUsage += HelpMessageOpt("-alertnotify=<cmd>", _("Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)"));
strUsage +=HelpMessageOpt("-assumevalid=<hex>", strprintf(_("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)"), defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex())); strUsage +=HelpMessageOpt("-assumevalid=<hex>", strprintf(_("If this block is in the chain assume that it and its ancestors are valid and potentially skip their script verification (0 to verify all, default: %s, testnet: %s)"), defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(), testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()));
strUsage += HelpMessageOpt("-blocksdir=<dir>", _("Specify blocks directory (default: <datadir>/blocks)"));
strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)")); strUsage += HelpMessageOpt("-blocknotify=<cmd>", _("Execute command when the best block changes (%s in cmd is replaced by block hash)"));
strUsage += HelpMessageOpt("-blockreconstructionextratxn=<n>", strprintf(_("Extra transactions to keep in memory for compact block reconstructions (default: %u)"), DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN)); strUsage += HelpMessageOpt("-blockreconstructionextratxn=<n>", strprintf(_("Extra transactions to keep in memory for compact block reconstructions (default: %u)"), DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN));
if (showDebug) if (showDebug)
@ -594,7 +595,7 @@ void CleanupBlockRevFiles()
// Remove the rev files immediately and insert the blk file paths into an // Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index. // ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n"); LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for -reindex with -prune\n");
fs::path blocksdir = GetDataDir() / "blocks"; fs::path blocksdir = GetBlocksDir();
for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) { for (fs::directory_iterator it(blocksdir); it != fs::directory_iterator(); it++) {
if (fs::is_regular_file(*it) && if (fs::is_regular_file(*it) &&
it->path().filename().string().length() == 12 && it->path().filename().string().length() == 12 &&
@ -897,6 +898,10 @@ bool AppInitParameterInteraction()
// also see: InitParameterInteraction() // also see: InitParameterInteraction()
if (!fs::is_directory(GetBlocksDir(false))) {
return InitError(strprintf(_("Specified blocks directory \"%s\" does not exist.\n"), gArgs.GetArg("-blocksdir", "").c_str()));
}
// if using block pruning, then disallow txindex // if using block pruning, then disallow txindex
if (gArgs.GetArg("-prune", 0)) { if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX))
@ -1622,7 +1627,7 @@ bool AppInitMain()
// ********************************************************* Step 10: import blocks // ********************************************************* Step 10: import blocks
if (!CheckDiskSpace()) if (!CheckDiskSpace() && !CheckDiskSpace(0, true))
return false; return false;
// Either install a handler to notify us when genesis activates, or set fHaveGenesis directly. // Either install a handler to notify us when genesis activates, or set fHaveGenesis directly.

View File

@ -623,7 +623,7 @@ int main(int argc, char *argv[])
if (!Intro::pickDataDirectory()) if (!Intro::pickDataDirectory())
return EXIT_SUCCESS; return EXIT_SUCCESS;
/// 6. Determine availability of data directory and parse bitcoin.conf /// 6. Determine availability of data and blocks directory and parse bitcoin.conf
/// - Do not call GetDataDir(true) before this step finishes /// - Do not call GetDataDir(true) before this step finishes
if (!fs::is_directory(GetDataDir(false))) if (!fs::is_directory(GetDataDir(false)))
{ {

View File

@ -147,7 +147,7 @@ size_t CCoinsViewDB::EstimateSize() const
return db.EstimateSize(DB_COIN, (char)(DB_COIN+1)); return db.EstimateSize(DB_COIN, (char)(DB_COIN+1));
} }
CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(GetDataDir() / "blocks" / "index", nCacheSize, fMemory, fWipe) { CBlockTreeDB::CBlockTreeDB(size_t nCacheSize, bool fMemory, bool fWipe) : CDBWrapper(gArgs.IsArgSet("-blocksdir") ? GetDataDir() / "blocks" / "index" : GetBlocksDir() / "index", nCacheSize, fMemory, fWipe) {
} }
bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) { bool CBlockTreeDB::ReadBlockFileInfo(int nFile, CBlockFileInfo &info) {

View File

@ -613,10 +613,41 @@ fs::path GetDefaultDataDir()
#endif #endif
} }
static fs::path g_blocks_path_cached;
static fs::path g_blocks_path_cache_net_specific;
static fs::path pathCached; static fs::path pathCached;
static fs::path pathCachedNetSpecific; static fs::path pathCachedNetSpecific;
static CCriticalSection csPathCached; static CCriticalSection csPathCached;
const fs::path &GetBlocksDir(bool fNetSpecific)
{
LOCK(csPathCached);
fs::path &path = fNetSpecific ? g_blocks_path_cache_net_specific : g_blocks_path_cached;
// This can be called during exceptions by LogPrintf(), so we cache the
// value so we don't have to do memory allocations after that.
if (!path.empty())
return path;
if (gArgs.IsArgSet("-blocksdir")) {
path = fs::system_complete(gArgs.GetArg("-blocksdir", ""));
if (!fs::is_directory(path)) {
path = "";
return path;
}
} else {
path = GetDataDir(false);
}
if (fNetSpecific)
path /= BaseParams().DataDir();
path /= "blocks";
fs::create_directories(path);
return path;
}
const fs::path &GetDataDir(bool fNetSpecific) const fs::path &GetDataDir(bool fNetSpecific)
{ {
@ -655,6 +686,8 @@ void ClearDatadirCache()
pathCached = fs::path(); pathCached = fs::path();
pathCachedNetSpecific = fs::path(); pathCachedNetSpecific = fs::path();
g_blocks_path_cached = fs::path();
g_blocks_path_cache_net_specific = fs::path();
} }
fs::path GetConfigFile(const std::string& confPath) fs::path GetConfigFile(const std::string& confPath)

View File

@ -183,6 +183,7 @@ void ReleaseDirectoryLocks();
bool TryCreateDirectories(const fs::path& p); bool TryCreateDirectories(const fs::path& p);
fs::path GetDefaultDataDir(); fs::path GetDefaultDataDir();
const fs::path &GetBlocksDir(bool fNetSpecific = true);
const fs::path &GetDataDir(bool fNetSpecific = true); const fs::path &GetDataDir(bool fNetSpecific = true);
void ClearDatadirCache(); void ClearDatadirCache();
fs::path GetConfigFile(const std::string& confPath); fs::path GetConfigFile(const std::string& confPath);

View File

@ -2108,7 +2108,7 @@ bool static FlushStateToDisk(const CChainParams& chainparams, CValidationState &
// Write blocks and block index to disk. // Write blocks and block index to disk.
if (fDoFullFlush || fPeriodicWrite) { if (fDoFullFlush || fPeriodicWrite) {
// Depend on nMinDiskSpace to ensure we can write block index // Depend on nMinDiskSpace to ensure we can write block index
if (!CheckDiskSpace(0)) if (!CheckDiskSpace(0, true))
return state.Error("out of disk space"); return state.Error("out of disk space");
// First make sure all block and undo data is flushed to disk. // First make sure all block and undo data is flushed to disk.
FlushBlockFile(); FlushBlockFile();
@ -2953,7 +2953,7 @@ static bool FindBlockPos(CDiskBlockPos &pos, unsigned int nAddSize, unsigned int
if (nNewChunks > nOldChunks) { if (nNewChunks > nOldChunks) {
if (fPruneMode) if (fPruneMode)
fCheckForPruning = true; fCheckForPruning = true;
if (CheckDiskSpace(nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos)) { if (CheckDiskSpace(nNewChunks * BLOCKFILE_CHUNK_SIZE - pos.nPos, true)) {
FILE *file = OpenBlockFile(pos); FILE *file = OpenBlockFile(pos);
if (file) { if (file) {
LogPrintf("Pre-allocating up to position 0x%x in blk%05u.dat\n", nNewChunks * BLOCKFILE_CHUNK_SIZE, pos.nFile); LogPrintf("Pre-allocating up to position 0x%x in blk%05u.dat\n", nNewChunks * BLOCKFILE_CHUNK_SIZE, pos.nFile);
@ -2986,7 +2986,7 @@ static bool FindUndoPos(CValidationState &state, int nFile, CDiskBlockPos &pos,
if (nNewChunks > nOldChunks) { if (nNewChunks > nOldChunks) {
if (fPruneMode) if (fPruneMode)
fCheckForPruning = true; fCheckForPruning = true;
if (CheckDiskSpace(nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos)) { if (CheckDiskSpace(nNewChunks * UNDOFILE_CHUNK_SIZE - pos.nPos, true)) {
FILE *file = OpenUndoFile(pos); FILE *file = OpenUndoFile(pos);
if (file) { if (file) {
LogPrintf("Pre-allocating up to position 0x%x in rev%05u.dat\n", nNewChunks * UNDOFILE_CHUNK_SIZE, pos.nFile); LogPrintf("Pre-allocating up to position 0x%x in rev%05u.dat\n", nNewChunks * UNDOFILE_CHUNK_SIZE, pos.nFile);
@ -3661,9 +3661,9 @@ static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfte
nLastBlockWeCanPrune, count); nLastBlockWeCanPrune, count);
} }
bool CheckDiskSpace(uint64_t nAdditionalBytes) bool CheckDiskSpace(uint64_t nAdditionalBytes, bool blocks_dir)
{ {
uint64_t nFreeBytesAvailable = fs::space(GetDataDir()).available; uint64_t nFreeBytesAvailable = fs::space(blocks_dir ? GetBlocksDir() : GetDataDir()).available;
// Check for nMinDiskSpace bytes (currently 50MB) // Check for nMinDiskSpace bytes (currently 50MB)
if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes) if (nFreeBytesAvailable < nMinDiskSpace + nAdditionalBytes)
@ -3706,7 +3706,7 @@ static FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly) {
fs::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix) fs::path GetBlockPosFilename(const CDiskBlockPos &pos, const char *prefix)
{ {
return GetDataDir() / "blocks" / strprintf("%s%05u.dat", prefix, pos.nFile); return GetBlocksDir() / strprintf("%s%05u.dat", prefix, pos.nFile);
} }
CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash) CBlockIndex * CChainState::InsertBlockIndex(const uint256& hash)

View File

@ -254,7 +254,7 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr<cons
bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex=nullptr, CBlockHeader *first_invalid=nullptr); bool ProcessNewBlockHeaders(const std::vector<CBlockHeader>& block, CValidationState& state, const CChainParams& chainparams, const CBlockIndex** ppindex=nullptr, CBlockHeader *first_invalid=nullptr);
/** Check whether enough disk space is available for an incoming block */ /** Check whether enough disk space is available for an incoming block */
bool CheckDiskSpace(uint64_t nAdditionalBytes = 0); bool CheckDiskSpace(uint64_t nAdditionalBytes = 0, bool blocks_dir = false);
/** Open a block file (blk?????.dat) */ /** Open a block file (blk?????.dat) */
FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly = false); FILE* OpenBlockFile(const CDiskBlockPos &pos, bool fReadOnly = false);
/** Translation to a filesystem path */ /** Translation to a filesystem path */

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python3
# Copyright (c) 2018 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the blocksdir option.
"""
from test_framework.test_framework import BitcoinTestFramework, initialize_datadir
import shutil
import os
class BlocksdirTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def run_test(self):
self.stop_node(0)
node0path = os.path.join(self.options.tmpdir, "node0")
shutil.rmtree(node0path)
initialize_datadir(self.options.tmpdir, 0)
self.log.info("Starting with non exiting blocksdir ...")
self.assert_start_raises_init_error(0, ["-blocksdir="+self.options.tmpdir+ "/blocksdir"], "Specified blocks director")
os.mkdir(self.options.tmpdir+ "/blocksdir")
self.log.info("Starting with exiting blocksdir ...")
self.start_node(0, ["-blocksdir="+self.options.tmpdir+ "/blocksdir"])
self.log.info("mining blocks..")
self.nodes[0].generate(10)
assert(os.path.isfile(os.path.join(self.options.tmpdir, "blocksdir", "regtest", "blocks", "blk00000.dat")))
assert(os.path.isdir(os.path.join(self.options.tmpdir, "node0", "regtest", "blocks", "index")))
if __name__ == '__main__':
BlocksdirTest().main()

View File

@ -136,6 +136,7 @@ BASE_SCRIPTS= [
'p2p_unrequested_blocks.py', 'p2p_unrequested_blocks.py',
'feature_logging.py', 'feature_logging.py',
'p2p_node_network_limited.py', 'p2p_node_network_limited.py',
'feature_blocksdir.py',
'feature_config_args.py', 'feature_config_args.py',
# Don't append tests at the end to avoid merge conflicts # Don't append tests at the end to avoid merge conflicts
# Put them in a random line within the section that fits their approximate run-time # Put them in a random line within the section that fits their approximate run-time