mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-08 10:31:50 -05:00
Compare commits
6 commits
88c35d2035
...
3a67a5c958
Author | SHA1 | Date | |
---|---|---|---|
|
3a67a5c958 | ||
|
b7f3b6fd96 | ||
|
690729577d | ||
|
a33aaadb2d | ||
|
7998eda034 | ||
|
0ad7d7abdb |
4 changed files with 92 additions and 50 deletions
|
@ -47,6 +47,7 @@ add_executable(test_bitcoin
|
||||||
blockmanager_tests.cpp
|
blockmanager_tests.cpp
|
||||||
bloom_tests.cpp
|
bloom_tests.cpp
|
||||||
bswap_tests.cpp
|
bswap_tests.cpp
|
||||||
|
chainstate_write_tests.cpp
|
||||||
checkqueue_tests.cpp
|
checkqueue_tests.cpp
|
||||||
cluster_linearize_tests.cpp
|
cluster_linearize_tests.cpp
|
||||||
coins_tests.cpp
|
coins_tests.cpp
|
||||||
|
|
45
src/test/chainstate_write_tests.cpp
Normal file
45
src/test/chainstate_write_tests.cpp
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// Copyright (c) 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 <test/util/setup_common.h>
|
||||||
|
#include <validation.h>
|
||||||
|
#include <validationinterface.h>
|
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(chainstate_write_tests)
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_CASE(chainstate_write_interval, TestingSetup)
|
||||||
|
{
|
||||||
|
struct TestSubscriber final : CValidationInterface {
|
||||||
|
bool m_did_flush{false};
|
||||||
|
void ChainStateFlushed(ChainstateRole, const CBlockLocator&) override
|
||||||
|
{
|
||||||
|
m_did_flush = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto sub{std::make_shared<TestSubscriber>()};
|
||||||
|
m_node.validation_signals->RegisterSharedValidationInterface(sub);
|
||||||
|
auto& chainstate{Assert(m_node.chainman)->ActiveChainstate()};
|
||||||
|
BlockValidationState state_dummy{};
|
||||||
|
|
||||||
|
// The first periodic flush sets m_next_write and does not flush
|
||||||
|
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
|
||||||
|
m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
||||||
|
BOOST_CHECK(!sub->m_did_flush);
|
||||||
|
|
||||||
|
// The periodic flush interval is between 50 and 70 minutes (inclusive)
|
||||||
|
SetMockTime(GetTime<std::chrono::minutes>() + 49min);
|
||||||
|
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
|
||||||
|
m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
||||||
|
BOOST_CHECK(!sub->m_did_flush);
|
||||||
|
|
||||||
|
SetMockTime(GetTime<std::chrono::minutes>() + 70min);
|
||||||
|
chainstate.FlushStateToDisk(state_dummy, FlushStateMode::PERIODIC);
|
||||||
|
m_node.validation_signals->SyncWithValidationInterfaceQueue();
|
||||||
|
BOOST_CHECK(sub->m_did_flush);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -90,10 +90,12 @@ using node::SnapshotMetadata;
|
||||||
|
|
||||||
/** Size threshold for warning about slow UTXO set flush to disk. */
|
/** Size threshold for warning about slow UTXO set flush to disk. */
|
||||||
static constexpr size_t WARN_FLUSH_COINS_SIZE = 1 << 30; // 1 GiB
|
static constexpr size_t WARN_FLUSH_COINS_SIZE = 1 << 30; // 1 GiB
|
||||||
/** Time to wait between writing blocks/block index to disk. */
|
/** Time window to wait between writing blocks/block index and chainstate to disk.
|
||||||
static constexpr std::chrono::hours DATABASE_WRITE_INTERVAL{1};
|
* Randomize writing time inside the window to prevent a situation where the
|
||||||
/** Time to wait between flushing chainstate to disk. */
|
* network over time settles into a few cohorts of synchronized writers.
|
||||||
static constexpr std::chrono::hours DATABASE_FLUSH_INTERVAL{24};
|
*/
|
||||||
|
static constexpr auto DATABASE_WRITE_INTERVAL_MIN{50min};
|
||||||
|
static constexpr auto DATABASE_WRITE_INTERVAL_MAX{70min};
|
||||||
/** Maximum age of our tip for us to be considered current for fee estimation */
|
/** Maximum age of our tip for us to be considered current for fee estimation */
|
||||||
static constexpr std::chrono::hours MAX_FEE_ESTIMATION_TIP_AGE{3};
|
static constexpr std::chrono::hours MAX_FEE_ESTIMATION_TIP_AGE{3};
|
||||||
const std::vector<std::string> CHECKLEVEL_DOC {
|
const std::vector<std::string> CHECKLEVEL_DOC {
|
||||||
|
@ -2833,7 +2835,6 @@ bool Chainstate::FlushStateToDisk(
|
||||||
try {
|
try {
|
||||||
{
|
{
|
||||||
bool fFlushForPrune = false;
|
bool fFlushForPrune = false;
|
||||||
bool fDoFullFlush = false;
|
|
||||||
|
|
||||||
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
|
CoinsCacheSizeState cache_state = GetCoinsCacheSizeState();
|
||||||
LOCK(m_blockman.cs_LastBlockFile);
|
LOCK(m_blockman.cs_LastBlockFile);
|
||||||
|
@ -2878,26 +2879,23 @@ bool Chainstate::FlushStateToDisk(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto nNow{SteadyClock::now()};
|
const auto nNow{NodeClock::now()};
|
||||||
// Avoid writing/flushing immediately after startup.
|
|
||||||
if (m_last_write == decltype(m_last_write){}) {
|
|
||||||
m_last_write = nNow;
|
|
||||||
}
|
|
||||||
if (m_last_flush == decltype(m_last_flush){}) {
|
|
||||||
m_last_flush = nNow;
|
|
||||||
}
|
|
||||||
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
|
// The cache is large and we're within 10% and 10 MiB of the limit, but we have time now (not in the middle of a block processing).
|
||||||
bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE;
|
bool fCacheLarge = mode == FlushStateMode::PERIODIC && cache_state >= CoinsCacheSizeState::LARGE;
|
||||||
// The cache is over the limit, we have to write now.
|
// The cache is over the limit, we have to write now.
|
||||||
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL;
|
bool fCacheCritical = mode == FlushStateMode::IF_NEEDED && cache_state >= CoinsCacheSizeState::CRITICAL;
|
||||||
// It's been a while since we wrote the block index to disk. Do this frequently, so we don't need to redownload after a crash.
|
// It's been a while since we wrote the block index and chain state to disk. Do this frequently, so we don't need to redownload or reindex after a crash.
|
||||||
bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow > m_last_write + DATABASE_WRITE_INTERVAL;
|
bool fPeriodicWrite = mode == FlushStateMode::PERIODIC && nNow >= m_next_write;
|
||||||
// It's been very long since we flushed the cache. Do this infrequently, to optimize cache usage.
|
// Combine all conditions that result in a write to disk.
|
||||||
bool fPeriodicFlush = mode == FlushStateMode::PERIODIC && nNow > m_last_flush + DATABASE_FLUSH_INTERVAL;
|
bool should_write = (mode == FlushStateMode::ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicWrite || fFlushForPrune;
|
||||||
// Combine all conditions that result in a full cache flush.
|
|
||||||
fDoFullFlush = (mode == FlushStateMode::ALWAYS) || fCacheLarge || fCacheCritical || fPeriodicFlush || fFlushForPrune;
|
if (should_write || m_next_write == NodeClock::time_point::max()) {
|
||||||
// Write blocks and block index to disk.
|
constexpr auto range{DATABASE_WRITE_INTERVAL_MAX - DATABASE_WRITE_INTERVAL_MIN};
|
||||||
if (fDoFullFlush || fPeriodicWrite) {
|
m_next_write = FastRandomContext().rand_uniform_delay(nNow + DATABASE_WRITE_INTERVAL_MIN, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write blocks, block index and best chain related state to disk.
|
||||||
|
if (should_write) {
|
||||||
// Ensure we can write block index
|
// Ensure we can write block index
|
||||||
if (!CheckDiskSpace(m_blockman.m_opts.blocks_dir)) {
|
if (!CheckDiskSpace(m_blockman.m_opts.blocks_dir)) {
|
||||||
return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!"));
|
return FatalError(m_chainman.GetNotifications(), state, _("Disk space is too low!"));
|
||||||
|
@ -2927,10 +2925,8 @@ bool Chainstate::FlushStateToDisk(
|
||||||
|
|
||||||
m_blockman.UnlinkPrunedFiles(setFilesToPrune);
|
m_blockman.UnlinkPrunedFiles(setFilesToPrune);
|
||||||
}
|
}
|
||||||
m_last_write = nNow;
|
|
||||||
}
|
if (!CoinsTip().GetBestBlock().IsNull()) {
|
||||||
// Flush best chain related state. This can only be done if the blocks / block index write was also done.
|
|
||||||
if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) {
|
|
||||||
if (coins_mem_usage >= WARN_FLUSH_COINS_SIZE) LogWarning("Flushing large (%d GiB) UTXO set to disk, it may take several minutes", coins_mem_usage >> 30);
|
if (coins_mem_usage >= WARN_FLUSH_COINS_SIZE) LogWarning("Flushing large (%d GiB) UTXO set to disk, it may take several minutes", coins_mem_usage >> 30);
|
||||||
LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d coins, %.2fKiB)",
|
LOG_TIME_MILLIS_WITH_CATEGORY(strprintf("write coins cache to disk (%d coins, %.2fKiB)",
|
||||||
coins_count, coins_mem_usage >> 10), BCLog::BENCH);
|
coins_count, coins_mem_usage >> 10), BCLog::BENCH);
|
||||||
|
@ -2948,16 +2944,16 @@ bool Chainstate::FlushStateToDisk(
|
||||||
if (empty_cache ? !CoinsTip().Flush() : !CoinsTip().Sync()) {
|
if (empty_cache ? !CoinsTip().Flush() : !CoinsTip().Sync()) {
|
||||||
return FatalError(m_chainman.GetNotifications(), state, _("Failed to write to coin database."));
|
return FatalError(m_chainman.GetNotifications(), state, _("Failed to write to coin database."));
|
||||||
}
|
}
|
||||||
m_last_flush = nNow;
|
|
||||||
full_flush_completed = true;
|
full_flush_completed = true;
|
||||||
TRACEPOINT(utxocache, flush,
|
TRACEPOINT(utxocache, flush,
|
||||||
int64_t{Ticks<std::chrono::microseconds>(SteadyClock::now() - nNow)},
|
int64_t{Ticks<std::chrono::microseconds>(NodeClock::now() - nNow)},
|
||||||
(uint32_t)mode,
|
(uint32_t)mode,
|
||||||
(uint64_t)coins_count,
|
(uint64_t)coins_count,
|
||||||
(uint64_t)coins_mem_usage,
|
(uint64_t)coins_mem_usage,
|
||||||
(bool)fFlushForPrune);
|
(bool)fFlushForPrune);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (full_flush_completed && m_chainman.m_options.signals) {
|
if (full_flush_completed && m_chainman.m_options.signals) {
|
||||||
// Update best block in wallet (so we can detect restored wallets).
|
// Update best block in wallet (so we can detect restored wallets).
|
||||||
m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), m_chain.GetLocator());
|
m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), m_chain.GetLocator());
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <util/fs.h>
|
#include <util/fs.h>
|
||||||
#include <util/hasher.h>
|
#include <util/hasher.h>
|
||||||
#include <util/result.h>
|
#include <util/result.h>
|
||||||
|
#include <util/time.h>
|
||||||
#include <util/translation.h>
|
#include <util/translation.h>
|
||||||
#include <versionbits.h>
|
#include <versionbits.h>
|
||||||
|
|
||||||
|
@ -802,8 +803,7 @@ private:
|
||||||
void UpdateTip(const CBlockIndex* pindexNew)
|
void UpdateTip(const CBlockIndex* pindexNew)
|
||||||
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||||
|
|
||||||
SteadyClock::time_point m_last_write{};
|
NodeClock::time_point m_next_write{NodeClock::time_point::max()};
|
||||||
SteadyClock::time_point m_last_flush{};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In case of an invalid snapshot, rename the coins leveldb directory so
|
* In case of an invalid snapshot, rename the coins leveldb directory so
|
||||||
|
|
Loading…
Add table
Reference in a new issue