Skip to content

Commit 252abd1

Browse files
jamesobryanofsky
andcommitted
init: add utxo snapshot detection
Add functionality for activating a snapshot-based chainstate if one is detected on-disk. Also cautiously initialize chainstate cache usages so that we don't somehow blow past our cache allowances during initialization, then rebalance at the end of init. Co-authored-by: Russell Yanofsky <[email protected]>
1 parent f9f1735 commit 252abd1

File tree

7 files changed

+87
-34
lines changed

7 files changed

+87
-34
lines changed

src/node/chainstate.cpp

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,15 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
4848
}
4949

5050
LOCK(cs_main);
51-
chainman.InitializeChainstate(options.mempool);
5251
chainman.m_total_coinstip_cache = cache_sizes.coins;
5352
chainman.m_total_coinsdb_cache = cache_sizes.coins_db;
5453

54+
// Load the fully validated chainstate.
55+
chainman.InitializeChainstate(options.mempool);
56+
57+
// Load a chain created from a UTXO snapshot, if any exist.
58+
chainman.DetectSnapshotChainstate(options.mempool);
59+
5560
auto& pblocktree{chainman.m_blockman.m_block_tree_db};
5661
// new CBlockTreeDB tries to delete the existing file, which
5762
// fails if it's still open from the previous loop. Close it first:
@@ -98,12 +103,20 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
98103
return {ChainstateLoadStatus::FAILURE, _("Error initializing block database")};
99104
}
100105

106+
// Conservative value which is arbitrarily chosen, as it will ultimately be changed
107+
// by a call to `chainman.MaybeRebalanceCaches()`. We just need to make sure
108+
// that the sum of the two caches (40%) does not exceed the allowable amount
109+
// during this temporary initialization state.
110+
double init_cache_fraction = 0.2;
111+
101112
// At this point we're either in reindex or we've loaded a useful
102113
// block tree into BlockIndex()!
103114

104115
for (Chainstate* chainstate : chainman.GetAll()) {
116+
LogPrintf("Initializing chainstate %s\n", chainstate->ToString());
117+
105118
chainstate->InitCoinsDB(
106-
/*cache_size_bytes=*/cache_sizes.coins_db,
119+
/*cache_size_bytes=*/chainman.m_total_coinsdb_cache * init_cache_fraction,
107120
/*in_memory=*/options.coins_db_in_memory,
108121
/*should_wipe=*/options.reindex || options.reindex_chainstate);
109122

@@ -125,7 +138,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
125138
}
126139

127140
// The on-disk coinsdb is now in a good state, create the cache
128-
chainstate->InitCoinsCache(cache_sizes.coins);
141+
chainstate->InitCoinsCache(chainman.m_total_coinstip_cache * init_cache_fraction);
129142
assert(chainstate->CanFlushToDisk());
130143

131144
if (!is_coinsview_empty(chainstate)) {
@@ -146,6 +159,11 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize
146159
};
147160
}
148161

162+
// Now that chainstates are loaded and we're able to flush to
163+
// disk, rebalance the coins caches to desired levels based
164+
// on the condition of each chainstate.
165+
chainman.MaybeRebalanceCaches();
166+
149167
return {ChainstateLoadStatus::SUCCESS, {}};
150168
}
151169

src/node/utxo_snapshot.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <logging.h>
99
#include <streams.h>
1010
#include <uint256.h>
11+
#include <util/system.h>
1112
#include <validation.h>
1213

1314
#include <cstdio>
@@ -76,4 +77,15 @@ std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
7677
return base_blockhash;
7778
}
7879

80+
std::optional<fs::path> FindSnapshotChainstateDir()
81+
{
82+
fs::path possible_dir =
83+
gArgs.GetDataDirNet() / fs::u8path(strprintf("chainstate%s", SNAPSHOT_CHAINSTATE_SUFFIX));
84+
85+
if (fs::exists(possible_dir)) {
86+
return possible_dir;
87+
}
88+
return std::nullopt;
89+
}
90+
7991
} // namespace node

src/node/utxo_snapshot.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,14 @@ bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
5858
std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
5959
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
6060

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

65+
66+
//! Return a path to the snapshot-based chainstate dir, if one exists.
67+
std::optional<fs::path> FindSnapshotChainstateDir();
68+
6369
} // namespace node
6470

6571
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
6363
// Create a snapshot-based chainstate.
6464
//
6565
const uint256 snapshot_blockhash = GetRandHash();
66-
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.InitializeChainstate(
66+
Chainstate& c2 = WITH_LOCK(::cs_main, return manager.ActivateExistingSnapshot(
6767
&mempool, snapshot_blockhash));
6868
chainstates.push_back(&c2);
6969

@@ -133,7 +133,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
133133

134134
// Create a snapshot-based chainstate.
135135
//
136-
Chainstate& c2 = WITH_LOCK(cs_main, return manager.InitializeChainstate(&mempool, GetRandHash()));
136+
Chainstate& c2 = WITH_LOCK(cs_main, return manager.ActivateExistingSnapshot(&mempool, GetRandHash()));
137137
chainstates.push_back(&c2);
138138
c2.InitCoinsDB(
139139
/*cache_size_bytes=*/1 << 23, /*in_memory=*/true, /*should_wipe=*/false);
@@ -383,7 +383,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_loadblockindex, TestChain100Setup)
383383
BOOST_CHECK_EQUAL(expected_assumed_valid, num_assumed_valid);
384384

385385
Chainstate& cs2 = WITH_LOCK(::cs_main,
386-
return chainman.InitializeChainstate(&mempool, GetRandHash()));
386+
return chainman.ActivateExistingSnapshot(&mempool, GetRandHash()));
387387

388388
reload_all_block_indexes();
389389

src/validation.cpp

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4744,28 +4744,15 @@ std::vector<Chainstate*> ChainstateManager::GetAll()
47444744
return out;
47454745
}
47464746

4747-
Chainstate& ChainstateManager::InitializeChainstate(
4748-
CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash)
4747+
Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
47494748
{
47504749
AssertLockHeld(::cs_main);
4751-
bool is_snapshot = snapshot_blockhash.has_value();
4752-
std::unique_ptr<Chainstate>& to_modify =
4753-
is_snapshot ? m_snapshot_chainstate : m_ibd_chainstate;
4750+
assert(!m_ibd_chainstate);
4751+
assert(!m_active_chainstate);
47544752

4755-
if (to_modify) {
4756-
throw std::logic_error("should not be overwriting a chainstate");
4757-
}
4758-
to_modify.reset(new Chainstate(mempool, m_blockman, *this, snapshot_blockhash));
4759-
4760-
// Snapshot chainstates and initial IBD chaintates always become active.
4761-
if (is_snapshot || (!is_snapshot && !m_active_chainstate)) {
4762-
LogPrintf("Switching active chainstate to %s\n", to_modify->ToString());
4763-
m_active_chainstate = to_modify.get();
4764-
} else {
4765-
throw std::logic_error("unexpected chainstate activation");
4766-
}
4767-
4768-
return *to_modify;
4753+
m_ibd_chainstate = std::make_unique<Chainstate>(mempool, m_blockman, *this);
4754+
m_active_chainstate = m_ibd_chainstate.get();
4755+
return *m_active_chainstate;
47694756
}
47704757

47714758
const AssumeutxoData* ExpectedAssumeutxo(
@@ -5134,3 +5121,31 @@ ChainstateManager::~ChainstateManager()
51345121
i.clear();
51355122
}
51365123
}
5124+
5125+
bool ChainstateManager::DetectSnapshotChainstate(CTxMemPool* mempool)
5126+
{
5127+
assert(!m_snapshot_chainstate);
5128+
std::optional<fs::path> path = node::FindSnapshotChainstateDir();
5129+
if (!path) {
5130+
return false;
5131+
}
5132+
std::optional<uint256> base_blockhash = node::ReadSnapshotBaseBlockhash(*path);
5133+
if (!base_blockhash) {
5134+
return false;
5135+
}
5136+
LogPrintf("[snapshot] detected active snapshot chainstate (%s) - loading\n",
5137+
fs::PathToString(*path));
5138+
5139+
this->ActivateExistingSnapshot(mempool, *base_blockhash);
5140+
return true;
5141+
}
5142+
5143+
Chainstate& ChainstateManager::ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
5144+
{
5145+
assert(!m_snapshot_chainstate);
5146+
m_snapshot_chainstate =
5147+
std::make_unique<Chainstate>(mempool, m_blockman, *this, base_blockhash);
5148+
LogPrintf("[snapshot] switching active chainstate to %s\n", m_snapshot_chainstate->ToString());
5149+
m_active_chainstate = m_snapshot_chainstate.get();
5150+
return *m_snapshot_chainstate;
5151+
}

src/validation.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -929,17 +929,11 @@ class ChainstateManager
929929
//! coins databases. This will be split somehow across chainstates.
930930
int64_t m_total_coinsdb_cache{0};
931931

932-
//! Instantiate a new chainstate and assign it based upon whether it is
933-
//! from a snapshot.
932+
//! Instantiate a new chainstate.
934933
//!
935934
//! @param[in] mempool The mempool to pass to the chainstate
936935
// constructor
937-
//! @param[in] snapshot_blockhash If given, signify that this chainstate
938-
//! is based on a snapshot.
939-
Chainstate& InitializeChainstate(
940-
CTxMemPool* mempool,
941-
const std::optional<uint256>& snapshot_blockhash = std::nullopt)
942-
LIFETIMEBOUND EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
936+
Chainstate& InitializeChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
943937

944938
//! Get all chainstates currently being used.
945939
std::vector<Chainstate*> GetAll();
@@ -1053,6 +1047,15 @@ class ChainstateManager
10531047
* information. */
10541048
void ReportHeadersPresync(const arith_uint256& work, int64_t height, int64_t timestamp);
10551049

1050+
//! When starting up, search the datadir for a chainstate based on a UTXO
1051+
//! snapshot that is in the process of being validated.
1052+
bool DetectSnapshotChainstate(CTxMemPool* mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1053+
1054+
//! Switch the active chainstate to one based on a UTXO snapshot that was loaded
1055+
//! previously.
1056+
Chainstate& ActivateExistingSnapshot(CTxMemPool* mempool, uint256 base_blockhash)
1057+
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
1058+
10561059
~ChainstateManager();
10571060
};
10581061

test/functional/feature_init.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ def check_clean_start():
5555
b'Loading P2P addresses',
5656
b'Loading banlist',
5757
b'Loading block index',
58-
b'Switching active chainstate',
5958
b'Checking all blk files are present',
6059
b'Loaded best chain:',
6160
b'init message: Verifying blocks',

0 commit comments

Comments
 (0)