The fix isn't enough. The file may be completely untrusted and corrupt, so just because the block height is a signed 32-bit, but positive integer, doesn't mean that the height is correct.
You'll have to check that the block height corresponds to the block hash, otherwise I fail to see how it can be used without adding bugs or risks or confusion. However, if you do the check, you may as well discard the deserialized value and just use the value from the check.
So again, my recommendation would be to remove it, because either:
- The value is untrusted, thus can not be used
- The value is checked, in which case it is redundant with the value from the check
An alternative would be to write a full file-content hash in the metadata as the second field (after the file magic), covering the whole file after the second field. This way, the file-content hash would be retrieved from the trusted chainparams, then calculated and compared. Afterwards the code can assume that all fields in the checked file are trusted to some extent, which would allow using the block height value.
However, that is a more involved fix, so my recommendation for now would be to drop the value.
Suggested diff:
diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h
index a7c4135787..bd5b68ba30 100644
--- a/src/node/utxo_snapshot.h
+++ b/src/node/utxo_snapshot.h
@@ -28,16 +28,17 @@ class Chainstate;
namespace node {
//! Metadata describing a serialized version of a UTXO set from which an
//! assumeutxo Chainstate can be constructed.
+//! All metadata fields come from an untrusted file, so must be validated
+//! before being used. Thus, new fields should be added only if needed.
class SnapshotMetadata
{
- const uint16_t m_version{1};
- const std::set<uint16_t> m_supported_versions{1};
+ static const uint16_t VERSION{2};
+ const std::set<uint16_t> m_supported_versions{VERSION};
const MessageStartChars m_network_magic;
public:
//! The hash of the block that reflects the tip of the chain for the
//! UTXO set contained in this snapshot.
uint256 m_base_blockhash;
- uint32_t m_base_blockheight;
//! The number of coins in the UTXO set contained in this snapshot. Used
@@ -54,15 +55,13 @@ public:
uint64_t coins_count) :
m_network_magic(network_magic),
m_base_blockhash(base_blockhash),
- m_base_blockheight(base_blockheight),
m_coins_count(coins_count) { }
template <typename Stream>
inline void Serialize(Stream& s) const {
s << SNAPSHOT_MAGIC_BYTES;
- s << m_version;
+ s << VERSION;
s << m_network_magic;
- s << m_base_blockheight;
s << m_base_blockhash;
s << m_coins_count;
}
@@ -98,7 +97,6 @@ public:
}
}
- s >> m_base_blockheight;
s >> m_base_blockhash;
s >> m_coins_count;
}
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 9899a13a1e..ee72fb2c20 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2862,10 +2862,12 @@ static RPCHelpMan loadtxoutset()
throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot: %s. (%s)", util::ErrorString(activation_result).original, path.utf8string()));
}
+ CBlockIndex& snap{*CHECK_NONFATAL(*activation_result)};
+
UniValue result(UniValue::VOBJ);
result.pushKV("coins_loaded", metadata.m_coins_count);
- result.pushKV("tip_hash", metadata.m_base_blockhash.ToString());
- result.pushKV("base_height", metadata.m_base_blockheight);
+ result.pushKV("tip_hash", snap.GetBlockHash().ToString());
+ result.pushKV("base_height", snap.nHeight);
result.pushKV("path", fs::PathToString(path));
return result;
},
diff --git a/src/validation.cpp b/src/validation.cpp
index 2b8f64e81a..0b101d1551 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -5633,31 +5633,31 @@ Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
return destroyed && !fs::exists(db_path);
}
-util::Result<void> ChainstateManager::ActivateSnapshot(
+util::Result<CBlockIndex*> ChainstateManager::ActivateSnapshot(
AutoFile& coins_file,
const SnapshotMetadata& metadata,
bool in_memory)
{
uint256 base_blockhash = metadata.m_base_blockhash;
- int base_blockheight = metadata.m_base_blockheight;
if (this->SnapshotBlockhash()) {
return util::Error{Untranslated("Can't activate a snapshot-based chainstate more than once")};
}
+ CBlockIndex* snapshot_start_block{};
+
{
LOCK(::cs_main);
if (!GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
auto available_heights = GetParams().GetAvailableSnapshotHeights();
std::string heights_formatted = util::Join(available_heights, ", ", [&](const auto& i) { return util::ToString(i); });
- return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s, height: %s). The following snapshot heights are available: %s"),
+ return util::Error{strprintf(Untranslated("assumeutxo block hash in snapshot metadata not recognized (hash: %s). The following snapshot heights are available: %s"),
base_blockhash.ToString(),
- base_blockheight,
heights_formatted)};
}
- CBlockIndex* snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
+ snapshot_start_block = m_blockman.LookupBlockIndex(base_blockhash);
if (!snapshot_start_block) {
return util::Error{strprintf(Untranslated("The base block header (%s) must appear in the headers chain. Make sure all headers are syncing, and call loadtxoutset again"),
base_blockhash.ToString())};
@@ -5668,7 +5668,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
return util::Error{strprintf(Untranslated("The base block header (%s) is part of an invalid chain"), base_blockhash.ToString())};
}
- if (!m_best_header || m_best_header->GetAncestor(base_blockheight) != snapshot_start_block) {
+ if (!m_best_header || m_best_header->GetAncestor(snapshot_start_block->nHeight) != snapshot_start_block) {
return util::Error{_("A forked headers-chain with more work than the chain with the snapshot base block header exists. Please proceed to sync without AssumeUtxo.")};
}
@@ -5782,7 +5782,7 @@ util::Result<void> ChainstateManager::ActivateSnapshot(
m_snapshot_chainstate->CoinsTip().DynamicMemoryUsage() / (1000 * 1000));
this->MaybeRebalanceCaches();
- return {};
+ return snapshot_start_block;
}
static void FlushSnapshotToDisk(CCoinsViewCache& coins_cache, bool snapshot_loaded)
diff --git a/src/validation.h b/src/validation.h
index 08e672c620..0638bbe983 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -1098,7 +1098,7 @@ public:
//! faking nTx* block index data along the way.
//! - Move the new chainstate to `m_snapshot_chainstate` and make it our
//! ChainstateActive().
- [[nodiscard]] util::Result<void> ActivateSnapshot(
+ [[nodiscard]] util::Result<CBlockIndex*> ActivateSnapshot(
AutoFile& coins_file, const node::SnapshotMetadata& metadata, bool in_memory);
//! Once the background validation chainstate has reached the height which