Skip to content

Commit f9f1735

Browse files
jamesobryanofsky
andcommitted
validation: rename snapshot chainstate dir
This changes the snapshot's leveldb chainstate dir name from `chainstate_[blockhash]` to `chainstate_snapshot`. This simplifies later logic that loads snapshot data, and enforces the limitation of a single snapshot at any given time. Since we still need to persis the blockhash of the base block, we write that out to a file (`chainstate_snapshot/base_blockhash`) for later use during initialization, so that we can reinitialize the snapshot chainstate. Co-authored-by: Russell Yanofsky <[email protected]>
1 parent d14bebf commit f9f1735

File tree

7 files changed

+134
-6
lines changed

7 files changed

+134
-6
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ libbitcoin_node_a_SOURCES = \
395395
node/minisketchwrapper.cpp \
396396
node/psbt.cpp \
397397
node/transaction.cpp \
398+
node/utxo_snapshot.cpp \
398399
node/validation_cache_args.cpp \
399400
noui.cpp \
400401
policy/fees.cpp \
@@ -900,6 +901,7 @@ libbitcoinkernel_la_SOURCES = \
900901
node/blockstorage.cpp \
901902
node/chainstate.cpp \
902903
node/interface_ui.cpp \
904+
node/utxo_snapshot.cpp \
903905
policy/feerate.cpp \
904906
policy/fees.cpp \
905907
policy/packages.cpp \

src/node/utxo_snapshot.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) 2022 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <node/utxo_snapshot.h>
6+
7+
#include <fs.h>
8+
#include <logging.h>
9+
#include <streams.h>
10+
#include <uint256.h>
11+
#include <validation.h>
12+
13+
#include <cstdio>
14+
#include <optional>
15+
16+
namespace node {
17+
18+
bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
19+
{
20+
AssertLockHeld(::cs_main);
21+
assert(snapshot_chainstate.m_from_snapshot_blockhash);
22+
23+
const std::optional<fs::path> chaindir = snapshot_chainstate.CoinsDB().StoragePath();
24+
assert(chaindir); // Sanity check that chainstate isn't in-memory.
25+
const fs::path write_to = *chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
26+
27+
FILE* file{fsbridge::fopen(write_to, "wb")};
28+
AutoFile afile{file};
29+
if (afile.IsNull()) {
30+
LogPrintf("[snapshot] failed to open base blockhash file for writing: %s\n",
31+
fs::PathToString(write_to));
32+
return false;
33+
}
34+
afile << *snapshot_chainstate.m_from_snapshot_blockhash;
35+
36+
if (afile.fclose() != 0) {
37+
LogPrintf("[snapshot] failed to close base blockhash file %s after writing\n",
38+
fs::PathToString(write_to));
39+
return false;
40+
}
41+
return true;
42+
}
43+
44+
std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
45+
{
46+
if (!fs::exists(chaindir)) {
47+
LogPrintf("[snapshot] cannot read base blockhash: no chainstate dir " /* Continued */
48+
"exists at path %s\n", fs::PathToString(chaindir));
49+
return std::nullopt;
50+
}
51+
const fs::path read_from = chaindir / node::SNAPSHOT_BLOCKHASH_FILENAME;
52+
const std::string read_from_str = fs::PathToString(read_from);
53+
54+
if (!fs::exists(read_from)) {
55+
LogPrintf("[snapshot] snapshot chainstate dir is malformed! no base blockhash file " /* Continued */
56+
"exists at path %s. Try deleting %s and calling loadtxoutset again?\n",
57+
fs::PathToString(chaindir), read_from_str);
58+
return std::nullopt;
59+
}
60+
61+
uint256 base_blockhash;
62+
FILE* file{fsbridge::fopen(read_from, "rb")};
63+
AutoFile afile{file};
64+
if (afile.IsNull()) {
65+
LogPrintf("[snapshot] failed to open base blockhash file for reading: %s\n",
66+
read_from_str);
67+
return std::nullopt;
68+
}
69+
afile >> base_blockhash;
70+
71+
if (std::fgetc(afile.Get()) != EOF) {
72+
LogPrintf("[snapshot] warning: unexpected trailing data in %s\n", read_from_str);
73+
} else if (std::ferror(afile.Get())) {
74+
LogPrintf("[snapshot] warning: i/o error reading %s\n", read_from_str);
75+
}
76+
return base_blockhash;
77+
}
78+
79+
} // namespace node

src/node/utxo_snapshot.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
#ifndef BITCOIN_NODE_UTXO_SNAPSHOT_H
77
#define BITCOIN_NODE_UTXO_SNAPSHOT_H
88

9+
#include <fs.h>
910
#include <uint256.h>
1011
#include <serialize.h>
12+
#include <validation.h>
13+
14+
#include <optional>
15+
16+
extern RecursiveMutex cs_main;
1117

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

3440
SERIALIZE_METHODS(SnapshotMetadata, obj) { READWRITE(obj.m_base_blockhash, obj.m_coins_count); }
3541
};
42+
43+
//! The file in the snapshot chainstate dir which stores the base blockhash. This is
44+
//! needed to reconstruct snapshot chainstates on init.
45+
//!
46+
//! Because we only allow loading a single snapshot at a time, there will only be one
47+
//! chainstate directory with this filename present within it.
48+
const fs::path SNAPSHOT_BLOCKHASH_FILENAME{"base_blockhash"};
49+
50+
//! Write out the blockhash of the snapshot base block that was used to construct
51+
//! this chainstate. This value is read in during subsequent initializations and
52+
//! used to reconstruct snapshot-based chainstates.
53+
bool WriteSnapshotBaseBlockhash(Chainstate& snapshot_chainstate)
54+
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
55+
56+
//! Read the blockhash of the snapshot base block that was used to construct the
57+
//! chainstate.
58+
std::optional<uint256> ReadSnapshotBaseBlockhash(fs::path chaindir)
59+
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
60+
61+
constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";
62+
3663
} // namespace node
3764

3865
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H

src/streams.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,12 +488,14 @@ class AutoFile
488488
AutoFile(const AutoFile&) = delete;
489489
AutoFile& operator=(const AutoFile&) = delete;
490490

491-
void fclose()
491+
int fclose()
492492
{
493+
int retval{0};
493494
if (file) {
494-
::fclose(file);
495+
retval = ::fclose(file);
495496
file = nullptr;
496497
}
498+
return retval;
497499
}
498500

499501
/** Get wrapped FILE* with transfer of ownership.

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,17 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
232232
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
233233
*chainman.SnapshotBlockhash());
234234

235-
// Ensure that the genesis block was not marked assumed-valid.
236-
BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid()));
235+
{
236+
LOCK(::cs_main);
237+
238+
// Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
239+
BOOST_CHECK_EQUAL(
240+
*node::ReadSnapshotBaseBlockhash(m_args.GetDataDirNet() / "chainstate_snapshot"),
241+
*chainman.SnapshotBlockhash());
242+
243+
// Ensure that the genesis block was not marked assumed-valid.
244+
BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
245+
}
237246

238247
const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
239248
const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());

src/validation.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,7 +1515,7 @@ void Chainstate::InitCoinsDB(
15151515
fs::path leveldb_name)
15161516
{
15171517
if (m_from_snapshot_blockhash) {
1518-
leveldb_name += "_" + m_from_snapshot_blockhash->ToString();
1518+
leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX;
15191519
}
15201520

15211521
m_coins_views = std::make_unique<CoinsViews>(
@@ -4837,9 +4837,17 @@ bool ChainstateManager::ActivateSnapshot(
48374837
static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
48384838
}
48394839

4840-
const bool snapshot_ok = this->PopulateAndValidateSnapshot(
4840+
bool snapshot_ok = this->PopulateAndValidateSnapshot(
48414841
*snapshot_chainstate, coins_file, metadata);
48424842

4843+
// If not in-memory, persist the base blockhash for use during subsequent
4844+
// initialization.
4845+
if (!in_memory) {
4846+
LOCK(::cs_main);
4847+
if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {
4848+
snapshot_ok = false;
4849+
}
4850+
}
48434851
if (!snapshot_ok) {
48444852
WITH_LOCK(::cs_main, this->MaybeRebalanceCaches());
48454853
return false;

test/lint/lint-circular-dependencies.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
EXPECTED_CIRCULAR_DEPENDENCIES = (
1515
"chainparamsbase -> util/system -> chainparamsbase",
1616
"node/blockstorage -> validation -> node/blockstorage",
17+
"node/utxo_snapshot -> validation -> node/utxo_snapshot",
1718
"policy/fees -> txmempool -> policy/fees",
1819
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel",
1920
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel",

0 commit comments

Comments
 (0)