aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames O'Beirne <james.obeirne@pm.me>2022-04-20 14:59:02 -0400
committerJames O'Beirne <james.obeirne@pm.me>2022-09-13 13:30:12 -0400
commitf9f1735f139b6a1f1c7fea50717ff90dc4ba2bce (patch)
treee6ac4570d221ba23b3778ffd42a84e04eea24a80
parentd14bebf100aaaa25c7558eeed8b5c536da99885f (diff)
downloadbitcoin-f9f1735f139b6a1f1c7fea50717ff90dc4ba2bce.tar.xz
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 <russ@yanofsky.org>
-rw-r--r--src/Makefile.am2
-rw-r--r--src/node/utxo_snapshot.cpp79
-rw-r--r--src/node/utxo_snapshot.h27
-rw-r--r--src/streams.h6
-rw-r--r--src/test/validation_chainstatemanager_tests.cpp13
-rw-r--r--src/validation.cpp12
-rwxr-xr-xtest/lint/lint-circular-dependencies.py1
7 files changed, 134 insertions, 6 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index bf26cc9674..cc5824c81e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -395,6 +395,7 @@ libbitcoin_node_a_SOURCES = \
node/minisketchwrapper.cpp \
node/psbt.cpp \
node/transaction.cpp \
+ node/utxo_snapshot.cpp \
node/validation_cache_args.cpp \
noui.cpp \
policy/fees.cpp \
@@ -900,6 +901,7 @@ libbitcoinkernel_la_SOURCES = \
node/blockstorage.cpp \
node/chainstate.cpp \
node/interface_ui.cpp \
+ node/utxo_snapshot.cpp \
policy/feerate.cpp \
policy/fees.cpp \
policy/packages.cpp \
diff --git a/src/node/utxo_snapshot.cpp b/src/node/utxo_snapshot.cpp
new file mode 100644
index 0000000000..29ff83a03b
--- /dev/null
+++ b/src/node/utxo_snapshot.cpp
@@ -0,0 +1,79 @@
+// 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 <validation.h>
+
+#include <cstdio>
+#include <optional>
+
+namespace node {
+
+bool WriteSnapshotBaseBlockhash(Chainstate& 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;
+}
+
+} // namespace node
diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h
index 9dd6f06997..b82a791092 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -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
@@ -33,6 +39,27 @@ public:
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(Chainstate& 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);
+
+constexpr std::string_view SNAPSHOT_CHAINSTATE_SUFFIX = "_snapshot";
+
} // namespace node
#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H
diff --git a/src/streams.h b/src/streams.h
index f14d347380..1bd98f8164 100644
--- a/src/streams.h
+++ b/src/streams.h
@@ -488,12 +488,14 @@ public:
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.
diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp
index 24ad9458c9..5aa38fc4bb 100644
--- a/src/test/validation_chainstatemanager_tests.cpp
+++ b/src/test/validation_chainstatemanager_tests.cpp
@@ -232,8 +232,17 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
*chainman.ActiveChainstate().m_from_snapshot_blockhash,
*chainman.SnapshotBlockhash());
- // Ensure that the genesis block was not marked assumed-valid.
- BOOST_CHECK(WITH_LOCK(::cs_main, return !chainman.ActiveChain().Genesis()->IsAssumedValid()));
+ {
+ LOCK(::cs_main);
+
+ // Note: WriteSnapshotBaseBlockhash() is implicitly tested above.
+ BOOST_CHECK_EQUAL(
+ *node::ReadSnapshotBaseBlockhash(m_args.GetDataDirNet() / "chainstate_snapshot"),
+ *chainman.SnapshotBlockhash());
+
+ // Ensure that the genesis block was not marked assumed-valid.
+ BOOST_CHECK(!chainman.ActiveChain().Genesis()->IsAssumedValid());
+ }
const AssumeutxoData& au_data = *ExpectedAssumeutxo(snapshot_height, ::Params());
const CBlockIndex* tip = WITH_LOCK(chainman.GetMutex(), return chainman.ActiveTip());
diff --git a/src/validation.cpp b/src/validation.cpp
index 402a962a04..0bfa17bb2b 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1515,7 +1515,7 @@ void Chainstate::InitCoinsDB(
fs::path leveldb_name)
{
if (m_from_snapshot_blockhash) {
- leveldb_name += "_" + m_from_snapshot_blockhash->ToString();
+ leveldb_name += node::SNAPSHOT_CHAINSTATE_SUFFIX;
}
m_coins_views = std::make_unique<CoinsViews>(
@@ -4837,9 +4837,17 @@ bool ChainstateManager::ActivateSnapshot(
static_cast<size_t>(current_coinstip_cache_size * SNAPSHOT_CACHE_PERC));
}
- const bool snapshot_ok = this->PopulateAndValidateSnapshot(
+ bool snapshot_ok = this->PopulateAndValidateSnapshot(
*snapshot_chainstate, coins_file, metadata);
+ // If not in-memory, persist the base blockhash for use during subsequent
+ // initialization.
+ if (!in_memory) {
+ LOCK(::cs_main);
+ if (!node::WriteSnapshotBaseBlockhash(*snapshot_chainstate)) {
+ snapshot_ok = false;
+ }
+ }
if (!snapshot_ok) {
WITH_LOCK(::cs_main, this->MaybeRebalanceCaches());
return false;
diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py
index a0f17ac119..abf38ca79b 100755
--- a/test/lint/lint-circular-dependencies.py
+++ b/test/lint/lint-circular-dependencies.py
@@ -14,6 +14,7 @@ import sys
EXPECTED_CIRCULAR_DEPENDENCIES = (
"chainparamsbase -> util/system -> chainparamsbase",
"node/blockstorage -> validation -> node/blockstorage",
+ "node/utxo_snapshot -> validation -> node/utxo_snapshot",
"policy/fees -> txmempool -> policy/fees",
"qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel",
"qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel",