Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions doc/design/assumeutxo.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ original chainstate remains in use as active.

Once the snapshot chainstate is loaded and validated, it is promoted to active
chainstate and a sync to tip begins. A new chainstate directory is created in the
datadir for the snapshot chainstate called
`chainstate_[SHA256 blockhash of snapshot base block]`.
datadir for the snapshot chainstate called `chainstate_snapshot`. When this directory
is present in the datadir, the snapshot chainstate will be detected and loaded as
active on node startup (via `DetectSnapshotChainstate()`).

| | |
| ---------- | ----------- |
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ libbitcoin_node_a_SOURCES = \
node/psbt.cpp \
node/transaction.cpp \
node/txreconciliation.cpp \
node/utxo_snapshot.cpp \
node/interface_ui.cpp \
noui.cpp \
policy/fees.cpp \
Expand Down
2 changes: 1 addition & 1 deletion src/dbwrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ static leveldb::Options GetOptions(size_t nCacheSize)
}

CDBWrapper::CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory, bool fWipe, bool obfuscate)
: m_name{fs::PathToString(path.stem())}
: m_name{fs::PathToString(path.stem())}, m_path{path}, m_is_memory{fMemory}
{
penv = nullptr;
readoptions.verify_checksums = true;
Expand Down
18 changes: 18 additions & 0 deletions src/dbwrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class dbwrapper_error : public std::runtime_error

class CDBWrapper;

namespace dbwrapper {
using leveldb::DestroyDB;
}

/** These should be considered an implementation detail of the specific database.
*/
namespace dbwrapper_private {
Expand Down Expand Up @@ -250,6 +254,12 @@ class CDBWrapper

std::vector<unsigned char> CreateObfuscateKey() const;

//! path to filesystem storage
const fs::path m_path;

//! whether or not the database resides in memory
bool m_is_memory;

public:
/**
* @param[in] path Location in the filesystem where leveldb data will be stored.
Expand Down Expand Up @@ -325,6 +335,14 @@ class CDBWrapper
return WriteBatch(batch, fSync);
}

//! @returns filesystem path to the on-disk data.
std::optional<fs::path> StoragePath() {
if (m_is_memory) {
return {};
}
return m_path;
}

template <typename K>
bool Exists(const K& key) const
{
Expand Down
10 changes: 10 additions & 0 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,16 @@ void BlockManager::FlushUndoFile(int block_file, bool finalize)
void BlockManager::FlushBlockFile(bool fFinalize, bool finalize_undo)
{
LOCK(cs_LastBlockFile);

if (m_blockfile_info.size() < 1) {
// Return if we haven't loaded any blockfiles yet. This happens during
// chainstate init, when we call ChainstateManager::MaybeRebalanceCaches() (which
// then calls FlushStateToDisk()), resulting in a call to this function before we
// have populated `m_blockfile_info` via LoadBlockIndexDB().
return;
}
assert(static_cast<int>(m_blockfile_info.size()) > m_last_blockfile);

FlatFilePos block_pos_old(m_last_blockfile, m_blockfile_info[m_last_blockfile].nSize);
if (!BlockFileSeq().Flush(block_pos_old, fFinalize)) {
AbortNode("Flushing block file to disk failed. This is likely the result of an I/O error.");
Expand Down
24 changes: 21 additions & 3 deletions src/node/chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
mnhf_manager.reset();
mnhf_manager = std::make_unique<CMNHFManager>(*evodb);

chainman.InitializeChainstate(mempool, *evodb, chain_helper);
chainman.m_total_coinstip_cache = nCoinCacheUsage;
chainman.m_total_coinsdb_cache = nCoinDBCache;

// Load the fully validated chainstate.
chainman.InitializeChainstate(mempool, *evodb, chain_helper);

// Load a chain created from a UTXO snapshot, if any exist.
chainman.DetectSnapshotChainstate(mempool, *evodb, chain_helper);

auto& pblocktree{chainman.m_blockman.m_block_tree_db};
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it first:
Expand Down Expand Up @@ -160,12 +165,20 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
return ChainstateLoadingError::ERROR_LOAD_GENESIS_BLOCK_FAILED;
}

// Conservative value which is arbitrarily chosen, as it will ultimately be changed
// by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure
// that the sum of the two caches (40%) does not exceed the allowable amount
// during this temporary initialization state.
double init_cache_fraction = 0.2;

// At this point we're either in reindex or we've loaded a useful
// block tree into BlockIndex()!

for (CChainState* chainstate : chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());

chainstate->InitCoinsDB(
/*cache_size_bytes=*/nCoinDBCache,
/*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction,
/*in_memory=*/coins_db_in_memory,
/*should_wipe=*/fReset || fReindexChainState);

Expand All @@ -185,7 +198,7 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
}

// The on-disk coinsdb is now in a good state, create the cache
chainstate->InitCoinsCache(nCoinCacheUsage);
chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction);
assert(chainstate->CanFlushToDisk());

// flush evodb
Expand Down Expand Up @@ -214,6 +227,11 @@ std::optional<ChainstateLoadingError> LoadChainstate(bool fReset,
return ChainstateLoadingError::ERROR_UPGRADING_EVO_DB;
}

// Now that chainstates are loaded and we're able to flush to
// disk, rebalance the coins caches to desired levels based
// on the condition of each chainstate.
chainman.MaybeRebalanceCaches();

return std::nullopt;
}

Expand Down
91 changes: 91 additions & 0 deletions src/node/utxo_snapshot.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) 2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <node/utxo_snapshot.h>

#include <fs.h>
#include <logging.h>
#include <streams.h>
#include <uint256.h>
#include <util/system.h>
#include <validation.h>

#include <cstdio>
#include <optional>

namespace node {

bool WriteSnapshotBaseBlockhash(CChainState& snapshot_chainstate)
{
AssertLockHeld(::cs_main);
assert(snapshot_chainstate.m_from_snapshot_blockhash);

const std::optional<fs::path> chaindir = snapshot_chainstate.CoinsDB().StoragePath();
assert(chaindir); // Sanity check that chainstate isn't in-memory.
const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;

FILE* file{fsbridge::fopen(write_to, "wb")};
AutoFile afile{file};
if (afile.IsNull()) {
LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n",
fs::PathToString(write_to));
return false;
}
afile << *snapshot_chainstate.m_from_snapshot_blockhash;

if (afile.fclose() != 0) {
LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n",
fs::PathToString(write_to));
return false;
}
return true;
}

std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
{
if (!fs::exists(chaindir)) {
LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */
"exists at path %s\n", fs::PathToString(chaindir));
return std::nullopt;
}
const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
const std::string read_from_str = fs::PathToString(read_from);

if (!fs::exists(read_from)) {
LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */
"exists at path %s. Try deleting %s and calling loadtxoutset again?\n",
fs::PathToString(chaindir), read_from_str);
return std::nullopt;
}

uint256 base_blockhash;
FILE* file{fsbridge::fopen(read_from, "rb")};
AutoFile afile{file};
if (afile.IsNull()) {
LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n",
read_from_str);
return std::nullopt;
}
afile >> base_blockhash;

if (std::fgetc(afile.Get()) != EOF) {
LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str);
} else if (std::ferror(afile.Get())) {
LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str);
}
return base_blockhash;
}

std::optional<fs::path> FindSnapshotChainstateDir()
{
fs::path possible_dir =
gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));

if (fs::exists(possible_dir)) {
return possible_dir;
}
return std::nullopt;
}

} // namespace node
33 changes: 33 additions & 0 deletions src/node/utxo_snapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@
#ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H
#define BITCOIN_NODE_UTXO_SNAPSHOT_H

#include <fs.h>
#include <uint256.h>
#include <serialize.h>
#include <validation.h>

#include <optional>

extern RecursiveMutex cs_main;

namespace node {
//! Metadata describing a serialized version of a UTXO set from which an
Expand All @@ -33,6 +39,33 @@ class SnapshotMetadata

SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); }
};

//! The file in the snapshot chainstate dir which stores the base blockhash. This is
//! needed to reconstruct snapshot chainstates on init.
//!
//! Because we only allow loading a single snapshot at a time, there will only be one
//! chainstate directory with this filename present within it.
const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"};

//! Write out the blockhash of the snapshot base block that was used to construct
//! this chainstate. This value is read in during subsequent initializations and
//! used to reconstruct snapshot-based chainstates.
bool WriteSnapshotBaseBlockhash(CChainState& snapshot_chainstate)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

//! Read the blockhash of the snapshot base block that was used to construct the
//! chainstate.
std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

//! Suffix appended to the chainstate (leveldb) dir when created based upon
//! a snapshot.
constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";


//! Return a path to the snapshot-based chainstate dir, if one exists.
std::optional<fs::path> FindSnapshotChainstateDir();

} // namespace node

#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H
6 changes: 4 additions & 2 deletions src/streams.h
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,14 @@ class AutoFile
AutoFile(const AutoFile&) = delete;
AutoFile& operator=(const AutoFile&) = delete;

void fclose()
int fclose()
{
int retval{0};
if (file) {
::fclose(file);
retval = ::fclose(file);
file = nullptr;
}
return retval;
}

/** Get wrapped FILE* with transfer of ownership.
Expand Down
36 changes: 28 additions & 8 deletions src/test/util/setup_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,7 @@ ChainTestingSetup::~ChainTestingSetup()
m_node.scheduler.reset();
}

TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const char*>& extra_args)
: ChainTestingSetup(chainName, extra_args)
void TestingSetup::LoadVerifyActivateChainstate()
{
const CChainParams& chainparams = Params();
// Ideally we'd move all the RPC tests to the functional testing framework
Expand Down Expand Up @@ -331,8 +330,8 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
m_cache_sizes.block_tree_db,
m_cache_sizes.coins_db,
m_cache_sizes.coins,
/*block_tree_db_in_memory=*/true,
/*coins_db_in_memory=*/true,
/*block_tree_db_in_memory=*/m_block_tree_db_in_memory,
/*coins_db_in_memory=*/m_coins_db_in_memory,
/*dash_dbs_in_memory=*/true);
assert(!maybe_load_error.has_value());

Expand Down Expand Up @@ -386,6 +385,18 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector<const
}
}

TestingSetup::TestingSetup(
const std::string& chainName,
const std::vector<const char*>& extra_args,
const bool coins_db_in_memory,
const bool block_tree_db_in_memory)
: ChainTestingSetup(chainName, extra_args),
m_coins_db_in_memory(coins_db_in_memory),
m_block_tree_db_in_memory(block_tree_db_in_memory)
{
LoadVerifyActivateChainstate();
}

TestingSetup::~TestingSetup()
{
m_node.peerman.reset();
Expand Down Expand Up @@ -414,13 +425,22 @@ TestingSetup::~TestingSetup()
DashChainstateSetupClose(m_node);
}

TestChain100Setup::TestChain100Setup(const std::string& chain_name, const std::vector<const char*>& extra_args)
: TestChainSetup{100, chain_name, extra_args}
TestChain100Setup::TestChain100Setup(
const std::string& chain_name,
const std::vector<const char*>& extra_args,
const bool coins_db_in_memory,
const bool block_tree_db_in_memory)
: TestChainSetup{100, chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory}
{
}

TestChainSetup::TestChainSetup(int num_blocks, const std::string& chain_name, const std::vector<const char*>& extra_args)
: TestingSetup{chain_name, extra_args}
TestChainSetup::TestChainSetup(
int num_blocks,
const std::string& chain_name,
const std::vector<const char*>& extra_args,
const bool coins_db_in_memory,
const bool block_tree_db_in_memory)
: TestingSetup{chain_name, extra_args, coins_db_in_memory, block_tree_db_in_memory}
{
SetMockTime(1598887952);
constexpr std::array<unsigned char, 32> vchKey = {
Expand Down
Loading
Loading